“The Go compiler uses a technique called escape analysis to figure out when some memory needs to live on the heap.”
The bug was rule := r[i]. That copies a struct, and returning a pointer to the copy forces it onto the heap on every match. Changing it to &r[i] took the runtime from 4.2 seconds to 2.4. Go hides memory management until it costs you 70 percent. The compiler will tell you, if you ask it.