There is another way to match a regular expression, and it does not backtrack at all. Instead of following one path and returning when it fails, it keeps every live path at once, as a set of positions in the pattern, and advances the whole set one character at a time.
Each character of the input is looked at once. The cost is the length of the input times the size of the pattern, and no input makes it hang — because there is nothing in it that can hang.
The automaton gives up two things, and they are the two things people reach for most. It has no captures — a set of states has no memory of which path it took, so there is nothing to ask about the pieces. And it cannot do backreferences or lookaround at all, for the same reason.
A backreference makes a pattern not regular in the formal sense: no finite automaton can remember an arbitrary earlier substring and check it again later. That is a fact about the language rather than a limitation of any implementation.
This is why Go's regexp and Rust's regex refuse backreferences and lookaround outright. They are automaton engines by choice, and the choice buys a guarantee: every pattern they accept runs in time proportional to the input, on every input, always.
It is a real trade and there is no right answer to it. What there is, is knowing which one you are holding — and whether the pattern you are about to ship has a division of the input that nobody has tried yet.