Rust, Reflection and Access Rules

  • It seems a bit weird to me to dedicate all this space to talking about access to private fields and respecting field access rules, and not even suggest the idea that a reflection API should actually provide different levels of information depending on where you're invoking it from. Which is to say, if I'm writing code inside the module that defines a type, then I should be able to reflect on that type's private fields as my code can otherwise access those private fields. And if I'm writing code from outside the module then I shouldn't be able to reflect on those private fields.

    One way to accomplish this would be to have reflection add a private method to a reflectable type that returns a mirror that includes information on the private fields. This does require the capability to reflect to be opt-in (and I think this article is assuming that reflection is globally enabled on all types rather than being opt-in), but reflection could plausibly be done as something like

      #[derive(Reflectable)]
      struct Foo {
          pub i: i32,
          x: i32
      }
    
    such that this expands to something like

      struct Foo {
          pub i: i32,
          x: i32
      }
    
      impl Foo {
        fn private_mirror() -> Mirror {
          /* construct mirror for all fields */
        }
      }
    
      impl Reflectable for Foo {
        fn mirror() -> Mirror {
          /* construct mirror for public fields */
        }
      }
    
    This way code from within the module can call `private_mirror()` to get a mirror to pass to reflection APIs (and Mirror could implement Reflectable to return itself like how Iterator implements IntoIterator) in order to do things like implement serialization.

  • If I learned right now that Rust had a reflection API then I’d assume that it’s fundamentally unsafe when it comes to handling non public fields. For exactly the reasons the article lists.

    I think the API with safe public reflection and unsafe private reflection is easy to motivate.

    The author doesn’t really provide any reason why it’s a bad idea other than “people might abuse it”. The SemVer argument I don’t understand: the versioning is for the public API. Nobody promises that somelib 1.0.0 and 1.0.1 behave remotely the same if I peek behind the curtain.

  • > Reflection interacts with the safety features of Rust in a somewhat counter-intuitive way. Those interactions force any reflection API to obey certain rules.

    I know enough Bevy and Rust to know that Bevy does have their own Reflection library (https://docs.rs/bevy_reflect/latest/bevy_reflect/), but I don't know the internals of it, anyone happens to know that who can compare it to what the author is ideaing about? As a Bevy user and library author, the Reflect API Bevy uses is simple enough at least.

  • Isn't reflection at odds with "zero-cost abstraction" principle?

  • Not clear on the problem.

    In Rust, each struct type, to be serialized, must have a serialization and deserialization function of its own. There's a macro to generate these by calling the appropriate serialization or deserialization function for each field.

    For the common types, there's a set of standard serialize and deserialize functions. Here's the list.[1] Those handle the special cases around Vec, Mutex, and such.

    The author doesn't make a strong case for reflection for other purposes.

    [1] https://docs.rs/serde/latest/serde/trait.Serialize.html

  • I prototyped https://github.com/emberian/serde-reflect if anyone is interested

  • I get what the article is saying. I enjoy Rust, as I enjoy dabbling in other more esoteric programming languages, like Zig. But one thing I miss from the days of early programming is the hacker spirit, in a way. As it master, I should be able to force my machine into giving me access of private fields. I want to be able to poke holes in stuff, to break things and at times, I want the ability to do something stupid just because I want to see what would happen. I feel the same for overly strict compilers. The number one thing I hate about Zig is the compiler treating me like a child when I have unreferenced variables.

    Modern languages should enable what older languages couldn‘t. They shouldn‘t get in my way needlessly.