|
| 1 | +# bevy_reflect: `Reflect` as a unique `Reflect` |
| 2 | + |
| 3 | +## Summary |
| 4 | + |
| 5 | +- create a new trait: `PartialReflect` |
| 6 | +- `Reflect: PartialReflect` |
| 7 | +- All methods on `Reflect` that **does not** depend on the uniqueness of the |
| 8 | + underlying type are moved to `PartialReflect` |
| 9 | +- Most things should now accept a `dyn PartialReflect` rather than |
| 10 | + `dyn Reflect`. |
| 11 | +- When possible, things should return a `dyn Reflect`. |
| 12 | +- It is possible to convert a `PartialReflect` into a `Reflect` using a |
| 13 | + `into_full` method. |
| 14 | +- `FromReflect` becomes `FromPartialReflect`. |
| 15 | + |
| 16 | +## Jargon |
| 17 | + |
| 18 | +For clarity, we will always call "new Reflect" the new version of Reflect, |
| 19 | +with guaranteed uniqueness, and "old Reflect" the current implementation of |
| 20 | +Reflect. |
| 21 | + |
| 22 | +#### pass for |
| 23 | + |
| 24 | +We say that old `T: Reflect` **passes for** old `U: Reflect` when `T`'s |
| 25 | +`type_name` is equal to `U`'s, and it is possible to convert from a `T` to a `U` |
| 26 | +using `apply`, `set` or `FromReflect::from_reflect`. |
| 27 | + |
| 28 | +#### underlying |
| 29 | + |
| 30 | +The **underlying** value of a dynamic object (eg: `dyn Reflect`) is the |
| 31 | +concrete type backing it. In the following code, the **underlying type** of |
| 32 | +`reflect_a` is `MysteryType`, while the **underlying value** is the value of |
| 33 | +`a`. |
| 34 | + |
| 35 | +```rust |
| 36 | +let a = MysteryType::new(); |
| 37 | +let reflect_a: Box<dyn Reflect> = Box::new(a.clone()) as Box<dyn Reflect>; |
| 38 | +``` |
| 39 | + |
| 40 | +When using `clone_value`, the underlying type changes: if `MysteryType` is a |
| 41 | +struct, the underlying type of `reflect_clone` will be `DynamicStruct`: |
| 42 | + |
| 43 | +```rust |
| 44 | +let reflect_clone: Box<dyn Reflect> = reflect_a.clone_value(); |
| 45 | +``` |
| 46 | + |
| 47 | +#### canonical type |
| 48 | + |
| 49 | +A `Box<dyn PartialReflect>` has a _canonical underlying type_ when the |
| 50 | +underlying type is the actual struct it is supposed to reflect. All types |
| 51 | +derived with `#[derive(Reflect)]` are canonical types. All new `Reflect`s are |
| 52 | +canonical types. |
| 53 | + |
| 54 | +#### proxies |
| 55 | + |
| 56 | +A `PartialReflect` (or old `Reflect`) implementor is said to be "proxy" when it |
| 57 | +doesn't have a canonical representation. The `Dynamic***` types are all proxies. |
| 58 | +Something that implements `PartialReflect` but not new `Reflect` is a proxy by |
| 59 | +definition. |
| 60 | + |
| 61 | +#### Language from previous versions of this RFC |
| 62 | + |
| 63 | +This RFC went through a few name changes, in order to understand previous |
| 64 | +discussion, here is a translation table: |
| 65 | +* `CanonReflect`: What is now the new `Reflect` |
| 66 | +* `ReflectProxy`: A version of what is now `PartialReflect`, but as a concrete |
| 67 | + enum rather than a trait |
| 68 | + |
| 69 | +## Motivation |
| 70 | + |
| 71 | +The old `Reflect` trait is a trap, it doesn't work as expected due to proxies |
| 72 | +mixing up with the "regular" types under the `dyn Reflect` trait object. |
| 73 | + |
| 74 | +When the underlying type of an old `Box<dyn Reflect>` is a proxy, a lot of |
| 75 | +the old `Reflect` methods become invalid, such as `any`, `reflect_hash`, |
| 76 | +`reflect_partial_eq`, `serializable`, `downcast`, `is` or `take`. Yet, the user |
| 77 | +can still call those methods, despite the fact that they are invalid in this |
| 78 | +condition. |
| 79 | + |
| 80 | +### What's under the old `dyn Reflect`? |
| 81 | + |
| 82 | +Currently, `Reflect` has very a confusing behavior. Most of the methods on |
| 83 | +old `Reflect` are highly dependent on the underlying type, regardless of whether |
| 84 | +they are supposed to stand for the requested type or not. |
| 85 | + |
| 86 | +For example |
| 87 | + |
| 88 | +```rust |
| 89 | +let a = MysteryType::new(); // suppose MysteryType implements Reflect |
| 90 | +let reflect_a: Box<dyn Reflect> = Box::new(a.clone()) as Box<dyn Reflect>; |
| 91 | +let dynamic_a: Box<dyn Reflect> = a.clone_value(); |
| 92 | +// This more or less always panic |
| 93 | +assert!(dynamic_a.is::<MyteryType>()) |
| 94 | +``` |
| 95 | + |
| 96 | +This is because multiple different things can pretend to be the same thing. In |
| 97 | +itself this shouldn't be an issue, but the old `Reflect` API is leaky, and |
| 98 | +you need to be aware of both this limitation and the underlying type to be able |
| 99 | +to use the old `Reflect` API correctly. |
| 100 | + |
| 101 | + |
| 102 | +### `reflect_hash` and co. with proxies |
| 103 | + |
| 104 | +The problem is not limited to `is`, but also extends to the `reflect_*` |
| 105 | +methods. |
| 106 | + |
| 107 | +```rust |
| 108 | +let a = MysteryType::new(); // suppose MysteryType implements Reflect |
| 109 | +let reflect_a: Box<dyn Reflect> = Box::new(a.clone()) as Box<dyn Reflect>; |
| 110 | +let dynamic_a: Box<dyn Reflect> = a.clone_value(); |
| 111 | +let reflect_hash = reflect_a.reflect_hash(); |
| 112 | +let dynamic_hash = dynamic_a.reflect_hash(); |
| 113 | +// This more or less always panic if `MysteryType` implements `Hash` |
| 114 | +assert_eq!(reflect_hash, dynamic_hash) |
| 115 | +``` |
| 116 | + |
| 117 | +### Problem |
| 118 | + |
| 119 | +Reflect has multiple distinct roles that require different assumptions on the |
| 120 | +underlying type: |
| 121 | +1. A trait for structural exploration of data structures independent from the |
| 122 | + data structure itself. Exposed by methods `reflect_ref`, `reflect_mut` and |
| 123 | + `apply` |
| 124 | +2. An extension to `Any` that allows generic construction from structural |
| 125 | + descriptions (automatic deserialization) through registry. |
| 126 | +3. "trait reflection" through registry. |
| 127 | + |
| 128 | +Role (1) and (2, 3) require different assumptions. As noted in the earlier |
| 129 | +sections, `Any` requires assumptions on the underlying types, it is the case of |
| 130 | +trait reflection as well. |
| 131 | + |
| 132 | +The problem stems from some methods of old `dyn Reflect` assuming that the |
| 133 | +underlying type is always the same. |
| 134 | + |
| 135 | +The following methods are the one that assumes uniqueness: |
| 136 | +- `any` |
| 137 | +- `any_mut` |
| 138 | +- `downcast` |
| 139 | +- `take` |
| 140 | +- `is` |
| 141 | +- `downcast_ref` |
| 142 | +- `downcast_mut` |
| 143 | +- `set` |
| 144 | + |
| 145 | +The `Any` trait bound on old `Reflect` is similarly problematic, as it introduces |
| 146 | +the same issues as those methods. |
| 147 | + |
| 148 | +Note that the following methods are also problematic, and require discussion, |
| 149 | +but to keep to scope of this RFC short, we will keep them in `PartialReflect`. |
| 150 | +- `reflect_hash` |
| 151 | +- `reflect_partial_eq` |
| 152 | +- `serializable` |
| 153 | + |
| 154 | +## Proposition |
| 155 | + |
| 156 | +This RFC separates the bits of `Reflect` needed for (1) and the bits needed for |
| 157 | +(2) and (3). |
| 158 | + |
| 159 | +We introduce `PartialReflect` to isolate the methods of `Reflect` for (1). This |
| 160 | +also means that the old `Reflect` subtraits now are bound to `PartialReflect` |
| 161 | +rather than `Reflect`. Proxy types such as `DynamicStruct` will also now only |
| 162 | +implement `PartialReflect` instead of full `Reflect`, because they do not make |
| 163 | +sense in the context of (2) and (3). |
| 164 | + |
| 165 | +This makes `Reflect` "canonical", in that the underlying type is the one being |
| 166 | +described all the time. And now the `Any` bound and any-related methods are all |
| 167 | +"safe" to use, because the assumptions requires to be able to use them are |
| 168 | +backed into the type system. |
| 169 | + |
| 170 | +We still probably want a way to convert from a `PartialReflect` to a `Reflect`, |
| 171 | +so we add a `into_full` method to `PartialReflect` to convert them into |
| 172 | +`Reflect` in cases where the underlying type is canonical. |
| 173 | + |
| 174 | +The `FromReflect` trait will be renamed `FromPartialReflect`, a future |
| 175 | +implementation might also add the ability to go from any _proxy_ partial |
| 176 | +reflect into a full reflect using a combination of `FromPartialReflect` and the |
| 177 | +type registry. |
| 178 | + |
| 179 | +In short: |
| 180 | +- create a new trait: `PartialReflect` |
| 181 | +- `Reflect: PartialReflect` |
| 182 | +- All methods on `Reflect` that **does not** depend on the uniqueness of the |
| 183 | + underlying type are moved to `PartialReflect` |
| 184 | +- Most things should now accept a `dyn PartialReflect` rather than |
| 185 | + `dyn Reflect`. |
| 186 | +- It is possible to convert a `PartialReflect` into a `Reflect` using a |
| 187 | + `into_full` method. |
| 188 | +- `FromReflect` becomes `FromPartialReflect`. |
| 189 | + |
| 190 | + |
| 191 | +### `PartialReflect` trait |
| 192 | + |
| 193 | +All methods that do not assume underlying uniqueness should be moved to a new |
| 194 | +trait: `PartialReflect`. `PartialReflect` also has a `as_full` and `into_full` |
| 195 | +to convert them into "full" `Reflect`. We also keep the "trait reflection" |
| 196 | +methods, because changing the trait reflection design is complex and we want to |
| 197 | +keep the scope of this RFC minimal. |
| 198 | + |
| 199 | +```rust |
| 200 | +pub trait PartialReflect: Send + Sync { |
| 201 | + // new |
| 202 | + fn as_full(&self) -> Option<&dyn Reflect>; |
| 203 | + fn into_full(self: Box<Self>) -> Result<Box<dyn Reflect>, Box<dyn PartialReflect>>; |
| 204 | + |
| 205 | + fn type_name(&self) -> &str; |
| 206 | + |
| 207 | + fn as_partial_reflect(&self) -> &dyn PartialReflect; |
| 208 | + fn as_partial_reflect_mut(&mut self) -> &mut dyn PartialReflect; |
| 209 | + fn reflect_ref(&self) -> ReflectRef; |
| 210 | + fn reflect_mut(&mut self) -> ReflectMut; |
| 211 | + |
| 212 | + fn apply(&mut self, value: &dyn PartialReflect); |
| 213 | + // Change name of `clone_value` |
| 214 | + // Ideally add documentation explaining that the underlying type changes. |
| 215 | + fn clone_partial(&self) -> Box<dyn PartialReflect>; |
| 216 | + |
| 217 | + fn debug(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {} |
| 218 | + |
| 219 | + // Questionable, but let's leave those for later. |
| 220 | + fn reflect_hash(&self) -> Option<u64> {} |
| 221 | + fn reflect_partial_eq(&self, _value: &dyn PartialReflect) -> Option<bool> {} |
| 222 | + fn serializable(&self) -> Option<Serializable> {} |
| 223 | +} |
| 224 | +``` |
| 225 | + |
| 226 | +### New `Reflect` trait |
| 227 | + |
| 228 | +`Reflect` now, instead of implementing those methods, has `PartialReflect` as |
| 229 | +trait bound: |
| 230 | + |
| 231 | +```rust |
| 232 | +pub trait Reflect: PartialReflect + Any { |
| 233 | + fn as_reflect(&self) -> &dyn Reflect; |
| 234 | + fn as_reflect_mut(&mut self) -> &mut dyn Reflect; |
| 235 | + |
| 236 | + fn any_mut(&mut self) -> &mut dyn Any { |
| 237 | + // implementation |
| 238 | + } |
| 239 | + // etc. |
| 240 | + fn any |
| 241 | + fn downcast |
| 242 | + fn take |
| 243 | + fn is |
| 244 | + fn downcast_ref |
| 245 | + fn downcast_mut |
| 246 | + fn set |
| 247 | +} |
| 248 | +``` |
| 249 | + |
| 250 | +This trait is automatically implemented by the `#[derive(Reflect)]` macro. |
| 251 | + |
| 252 | +Note that when [trait upcasting] is implemented, the `any_mut` and other |
| 253 | +`Any`-related methods should be moved into a `impl dyn Reflect` block, |
| 254 | +`as_reflect` and `as_partial_reflect` can also be dropped with trait |
| 255 | +upcasting. |
| 256 | + |
| 257 | +### `PartialReflect` to `Reflect` conversion |
| 258 | + |
| 259 | +We still want to be able to use the `Any` methods on our `PartialReflect`, so we |
| 260 | +want a way to get a new `Reflect` from a `PartialReflect`. This is only possible |
| 261 | +by checking if the underlying type is the one we expect OR converting into the |
| 262 | +underlying type described by the `PartialReflect::type_name()` method. |
| 263 | + |
| 264 | +To do so, we have two options: |
| 265 | +1. Provide an expected type or access a constructor stored in type registry |
| 266 | + and convert with `FromReflect` from any `Reflect` to the canonical |
| 267 | + representation of a `Reflect`. |
| 268 | +2. Add a `into_full(self: Box<Self>) -> Option<Box<dyn Reflect>>` method to |
| 269 | + `PartialReflect` that returns `Some` only for canonical types. |
| 270 | + |
| 271 | +(1) Is complex and requires a lot more design, so we'll stick with (2) for this |
| 272 | +RFC. |
| 273 | + |
| 274 | +`into_full` will return `None` by default, but in the `#[derive(Reflect)]` |
| 275 | +macro will return `Some(self)`. Converting from a proxy `PartialReflect` |
| 276 | +to a `Reflect` is currently impossible. |
| 277 | + |
| 278 | +## User-facing explanation |
| 279 | + |
| 280 | +`PartialReflect` let you navigate any type independently of their shape |
| 281 | +with the `reflect_ref` and `reflect_mut` methods. `Reflect` is a special |
| 282 | +case of `PartialReflect` that also let you convert into a concrete type using the |
| 283 | +`Any` methods. To get a `Reflect` from a `PartialReflect`, you should use |
| 284 | +`PartialReflect::into_full`. |
| 285 | + |
| 286 | +Any type derived with `#[derive(Reflect)]` implements both `Reflect` and |
| 287 | +`PartialReflect`. The only types that implements `PartialReflect` but not |
| 288 | +`Reflect` are "proxy" types such as `DynamicStruct`, or any third party |
| 289 | +equivalent. |
| 290 | + |
| 291 | +## Drawbacks |
| 292 | + |
| 293 | +- It is impossible to make a proxy `PartialReflect` into a `Reflect` without |
| 294 | + knowing the underlying type. |
| 295 | +- This splits the reflect API, that used to be neatly uniform. It will be |
| 296 | + more difficult to combine various use cases of `bevy_reflect`, requiring |
| 297 | + explicit conversions. (Note that however, the previous state of things would |
| 298 | + result in things getting silently broken) |
| 299 | + |
| 300 | +## Unresolved questions |
| 301 | + |
| 302 | +- Should `Reflect` still be named `Reflect`, does it need to be a subtrait of |
| 303 | + `PartialReflect`? |
| 304 | +- Is the ability to convert from a proxy `PartialReflect` to `Reflect` a |
| 305 | + hard-requirement, ie: blocker? (I think only a first attempt at implementation |
| 306 | + can answer this) |
| 307 | + |
| 308 | +## Future possibilities |
| 309 | + |
| 310 | +- Could we modify the `ReflectRef` and `ReflectMut` enums to enable a sort of |
| 311 | + "partial" evaluation of structures by adding a `&dyn Reflect` variant? |
| 312 | +- `impl<T: Struct> PartialReflect for T {}` and other subtraits rather than |
| 313 | + `Struct: PartialReflect`. |
| 314 | +- Add a `reflect_owned` that returns a `Dynamic` equivalent of `ReflectRef` |
| 315 | + (As an earlier version of this RFC called `ReflectProxy`) |
| 316 | +- Make use of [trait upcasting]. |
| 317 | +- An earlier version of this RFC combined the `FromReflect` trait with the |
| 318 | + `TypeRegistry` to enable conversion from any proxy `PartialReflect` to |
| 319 | + concrete `Reflect`, this method is probably still needed. |
| 320 | +- (2) and (3) are still worth discriminating, this relates to the `reflect_hash` |
| 321 | + and `reflect_partial_eq` methods, which are explicitly left "broken as before" |
| 322 | + in this RFC. |
| 323 | + |
| 324 | +[trait upcasting]: https://github.com/rust-lang/rust/issues/65991 |
0 commit comments