In my ray-tracer (Glome), I treat instances as just a container for some other object along with a transformation matrix, that itself behaves like a regular object. Every kind of object has a method to construct a bounding box, so that when building an acceleration structure that contains instances, if I remember correctly, I take the 8 corners of the bounding box of the contained object, transform them, and constructs a new bounding box around the transformed points. It's not optimal, but it works okay.
Another way of working around your problem is to just pass the current instance (or a list of instances) as an argument to your ray intersection function. (I use a similar trick for textures, and also for a tagging scheme where I can apply arbitrary tags to objects and then pass back a list of all the tags of objects that the ray hit when I return a ray intersection.)
Sending the instance to the geometry to fill it out in the struct is a good idea, if Rust had default parameter support I'd do this and have the instance's intersect just default to None and pass itself to the geometry's intersect. Then the functions would have the same signature and we could mark the case of a None instance in geometry as unreachable.
Generally it's advisable to templatize acceleration structures so they can natively cope with triangles, objects and possibly other primitives (spheres and curves) separately.