Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

rename bevy_reflect's Array #7059

Closed
soqb opened this issue Dec 29, 2022 · 9 comments
Closed

rename bevy_reflect's Array #7059

soqb opened this issue Dec 29, 2022 · 9 comments
Labels
A-Reflection Runtime information about types C-Usability A targeted quality-of-life change that makes Bevy easier to use S-Ready-For-Implementation This issue is ready for an implementation PR. Go for it!

Comments

@soqb
Copy link
Contributor

soqb commented Dec 29, 2022

What problem does this solve or what need does it fill?

The term "array" in Rust is used most commonly to describe a linear array defined by Rust's array syntax (e.g. [u8; 4]) which has a strict, stable in-memory layout. Less frequently and more conceptually, the term may also describe a span of homogeneously-sized elements (typically bytes) in memory ({ start: *const T, end: *const T } is occasionally thought of as an array).

We have a data type in bevy_reflect (i.e. a variant of ReflectRef with a matching trait providing core functionality) called Array. Importantly, the Array trait is a supertrait of List.

Although the trait provides the same core functionality as a Rust array (it has a fixed-length and access-by-index), the name "array" is confusing:

  • Types implementing Array are not necessarily fixed-length as arrays are. In fact, all types implementing List have variable length.
  • Types implementing Array are not necessarily backed by an array (by either definition) either and this could lead to dissonance trying to understand the relationship between bevy_reflect and the type.
    • Vec is backed by the second definition of array described above but this is an implementation detail subject (although unlikely) to change.
    • LinkedLists are spatially disjoint, heap-allocated nodes and definitely not arrays except in the most general possible case.
    • VecDeque is perhaps the worst offender, implemented IIRC similarly to Vec but where the first element of the underlying array is not necessarily the first element in the deque.

What solution would you like?

Rename the Array trait, DynamicArray, and the Array variant of ReflectRef, ReflectMut and ReflectOwned as well as any conceptual docs using the term "array" in this context.

A name I proposed was Sequence inspired by serde's primitive data type of the same name, although the word is rarely used in my experience and serde's sequence does not have to provide a length during serialisation (similar to the Iterator trait).

What alternative(s) have you considered?

Leave name as is. Potential confusion may be mitigated by clear docs.

Additional context

Originally briefly discussed with @MrGVSV on discord.

@soqb soqb added C-Feature A new feature, making something new possible S-Needs-Triage This issue needs to be labelled labels Dec 29, 2022
@alice-i-cecile alice-i-cecile added C-Usability A targeted quality-of-life change that makes Bevy easier to use A-Reflection Runtime information about types and removed C-Feature A new feature, making something new possible S-Needs-Triage This issue needs to be labelled labels Dec 29, 2022
@alice-i-cecile
Copy link
Member

alice-i-cecile commented Dec 29, 2022

I'm on board with this rename. Sequence seems like a good option.

@MrGVSV MrGVSV added the D-Trivial Nice and easy! A great choice to get started with Bevy label Dec 31, 2022
@soqb
Copy link
Contributor Author

soqb commented Jan 1, 2023

perhaps also Indexable or Elements?

@alice-i-cecile
Copy link
Member

IMO sequence > indexable >> elements.

@stefanmitic
Copy link

stefanmitic commented Jan 5, 2023

Just wanna make sure I'm on the right track before I submit anything since I'm new around here (Hi! 😃).

The main affected files are bevy_reflect/src/array.rs containing the Array trait and accompanying implementations and bevy_reflect/src/reflect.rs containing the enums? And also everything affected by the rename operation.

If this is correct I would like to tackle this change as my first submit. If not, could you please point me in the right direction?
Thanks!

@alice-i-cecile
Copy link
Member

@stefanmitic this seems correct: feel free to make the PR and we'll verify there!

@MrGVSV
Copy link
Member

MrGVSV commented Jan 7, 2023

Thinking on this some more, I wanted to point out another option could be to separate List from Array.

I think the only reason they're connected is because they share similar methods and logic. However, because of this we have a few issues:

  1. As mentioned in the issue, List implementors break the apparent "contract" that Array is fixed-length
  2. It's not immediately clear what certain reflection methods expect/return, including (but not limited to) Reflect::reflect_ref, Reflect::get_type_info, and Reflect::apply
  3. It's not immediately clear how other traits should be implemented, such as FromReflect and Typed
  4. There is an unfortunate collision between Array::clone_dynamic and List::clone_dynamic

If we separated them then most of these issues go away. Consumers of dyn Array could be more confident that Array::len is generally fixed (obviously it's up to the implementor to uphold that), and there would be no question of how those reflection traits work— they work in the same way as for other reflection kinds.

Additionally, it might make future work simpler since we no longer need to worry about possible complexities around subtraits of subtraits (if any).

The only downside (at least the only one I can think of right now) is that we can no longer pass data around as both dyn List and dyn Array. However, this is already severely limited as Reflect::reflect_ref and friends can only return one or the other. This means that the only way to pass data as both traits is to start with a concrete type and then cast it to the desired trait object. This cannot be done on a dyn Reflect, dyn List, or dyn Array without a concrete type to cast to and from.

And if we really want to, we could add a List::into_array method to turn it into something like an ArrayList(Box<dyn List>) struct, where ArrayList implements Array using the contained dyn List.

So separating them could be a potential solution here. Any thoughts on this?

@alice-i-cecile
Copy link
Member

This is a compelling argument: by simplifying the trait hierarchy we can clearly seperate the two different classes of data structure.

I think this is more intuitive and gets rid of complexity that doesn't seem to be pulling its weight.

@soqb
Copy link
Contributor Author

soqb commented Jan 7, 2023

i like this course of action.

i'll leave this issue open for now until one suggesting the split opens up (if not i'll make one myself).

@alice-i-cecile alice-i-cecile added S-Ready-For-Implementation This issue is ready for an implementation PR. Go for it! and removed D-Trivial Nice and easy! A great choice to get started with Bevy labels Jan 7, 2023
@soqb
Copy link
Contributor Author

soqb commented Jan 7, 2023

closed in favour of #7121.

@soqb soqb closed this as completed Jan 7, 2023
@MrGVSV MrGVSV closed this as not planned Won't fix, can't repro, duplicate, stale Jan 8, 2023
bors bot pushed a commit that referenced this issue Feb 13, 2023
# Objective

Resolves #7121

## Solution

Decouples `List` and `Array` by removing `Array` as a supertrait of `List`. Additionally, similar methods from `Array` have been added to `List` so that their usages can remain largely unchanged.

#### Possible Alternatives

##### `Sequence`

My guess for why we originally made `List` a subtrait of `Array` is that they share a lot of common operations. We could potentially move these overlapping methods to a `Sequence` (name taken from #7059) trait and make that a supertrait of both. This would allow functions to contain logic that simply operates on a sequence rather than "list vs array".

However, this means that we'd need to add methods for converting to a `dyn Sequence`. It also might be confusing since we wouldn't add a `ReflectRef::Sequence` or anything like that. Is such a trait worth adding (either in this PR or a followup one)? 

---

## Changelog

- Removed `Array` as supertrait of `List`
  - Added methods to `List` that were previously provided by `Array`

## Migration Guide

The `List` trait is no longer dependent on `Array`. Implementors of `List` can remove the `Array` impl and move its methods into the `List` impl (with only a couple tweaks).

```rust
// BEFORE
impl Array for Foo {
  fn get(&self, index: usize) -> Option<&dyn Reflect> {/* ... */}
  fn get_mut(&mut self, index: usize) -> Option<&mut dyn Reflect> {/* ... */}
  fn len(&self) -> usize {/* ... */}
  fn is_empty(&self) -> bool {/* ... */}
  fn iter(&self) -> ArrayIter {/* ... */}
  fn drain(self: Box<Self>) -> Vec<Box<dyn Reflect>> {/* ... */}
  fn clone_dynamic(&self) -> DynamicArray {/* ... */}
}

impl List for Foo {
  fn insert(&mut self, index: usize, element: Box<dyn Reflect>) {/* ... */}
  fn remove(&mut self, index: usize) -> Box<dyn Reflect> {/* ... */}
  fn push(&mut self, value: Box<dyn Reflect>) {/* ... */}
  fn pop(&mut self) -> Option<Box<dyn Reflect>> {/* ... */}
  fn clone_dynamic(&self) -> DynamicList {/* ... */}
}

// AFTER
impl List for Foo {
  fn get(&self, index: usize) -> Option<&dyn Reflect> {/* ... */}
  fn get_mut(&mut self, index: usize) -> Option<&mut dyn Reflect> {/* ... */}
  fn insert(&mut self, index: usize, element: Box<dyn Reflect>) {/* ... */}
  fn remove(&mut self, index: usize) -> Box<dyn Reflect> {/* ... */}
  fn push(&mut self, value: Box<dyn Reflect>) {/* ... */}
  fn pop(&mut self) -> Option<Box<dyn Reflect>> {/* ... */}
  fn len(&self) -> usize {/* ... */}
  fn is_empty(&self) -> bool {/* ... */}
  fn iter(&self) -> ListIter {/* ... */}
  fn drain(self: Box<Self>) -> Vec<Box<dyn Reflect>> {/* ... */}
  fn clone_dynamic(&self) -> DynamicList {/* ... */}
}
```

Some other small tweaks that will need to be made include:
- Use `ListIter` for `List::iter` instead of `ArrayIter` (the return type from `Array::iter`)
- Replace `array_hash` with `list_hash` in `Reflect::reflect_hash` for implementors of `List`
myreprise1 pushed a commit to myreprise1/bevy that referenced this issue Feb 14, 2023
Resolves bevyengine#7121

Decouples `List` and `Array` by removing `Array` as a supertrait of `List`. Additionally, similar methods from `Array` have been added to `List` so that their usages can remain largely unchanged.

My guess for why we originally made `List` a subtrait of `Array` is that they share a lot of common operations. We could potentially move these overlapping methods to a `Sequence` (name taken from bevyengine#7059) trait and make that a supertrait of both. This would allow functions to contain logic that simply operates on a sequence rather than "list vs array".

However, this means that we'd need to add methods for converting to a `dyn Sequence`. It also might be confusing since we wouldn't add a `ReflectRef::Sequence` or anything like that. Is such a trait worth adding (either in this PR or a followup one)?

---

- Removed `Array` as supertrait of `List`
  - Added methods to `List` that were previously provided by `Array`

The `List` trait is no longer dependent on `Array`. Implementors of `List` can remove the `Array` impl and move its methods into the `List` impl (with only a couple tweaks).

```rust
// BEFORE
impl Array for Foo {
  fn get(&self, index: usize) -> Option<&dyn Reflect> {/* ... */}
  fn get_mut(&mut self, index: usize) -> Option<&mut dyn Reflect> {/* ... */}
  fn len(&self) -> usize {/* ... */}
  fn is_empty(&self) -> bool {/* ... */}
  fn iter(&self) -> ArrayIter {/* ... */}
  fn drain(self: Box<Self>) -> Vec<Box<dyn Reflect>> {/* ... */}
  fn clone_dynamic(&self) -> DynamicArray {/* ... */}
}

impl List for Foo {
  fn insert(&mut self, index: usize, element: Box<dyn Reflect>) {/* ... */}
  fn remove(&mut self, index: usize) -> Box<dyn Reflect> {/* ... */}
  fn push(&mut self, value: Box<dyn Reflect>) {/* ... */}
  fn pop(&mut self) -> Option<Box<dyn Reflect>> {/* ... */}
  fn clone_dynamic(&self) -> DynamicList {/* ... */}
}

// AFTER
impl List for Foo {
  fn get(&self, index: usize) -> Option<&dyn Reflect> {/* ... */}
  fn get_mut(&mut self, index: usize) -> Option<&mut dyn Reflect> {/* ... */}
  fn insert(&mut self, index: usize, element: Box<dyn Reflect>) {/* ... */}
  fn remove(&mut self, index: usize) -> Box<dyn Reflect> {/* ... */}
  fn push(&mut self, value: Box<dyn Reflect>) {/* ... */}
  fn pop(&mut self) -> Option<Box<dyn Reflect>> {/* ... */}
  fn len(&self) -> usize {/* ... */}
  fn is_empty(&self) -> bool {/* ... */}
  fn iter(&self) -> ListIter {/* ... */}
  fn drain(self: Box<Self>) -> Vec<Box<dyn Reflect>> {/* ... */}
  fn clone_dynamic(&self) -> DynamicList {/* ... */}
}
```

Some other small tweaks that will need to be made include:
- Use `ListIter` for `List::iter` instead of `ArrayIter` (the return type from `Array::iter`)
- Replace `array_hash` with `list_hash` in `Reflect::reflect_hash` for implementors of `List`
myreprise1 pushed a commit to myreprise1/bevy that referenced this issue Feb 15, 2023
Resolves bevyengine#7121

Decouples `List` and `Array` by removing `Array` as a supertrait of `List`. Additionally, similar methods from `Array` have been added to `List` so that their usages can remain largely unchanged.

My guess for why we originally made `List` a subtrait of `Array` is that they share a lot of common operations. We could potentially move these overlapping methods to a `Sequence` (name taken from bevyengine#7059) trait and make that a supertrait of both. This would allow functions to contain logic that simply operates on a sequence rather than "list vs array".

However, this means that we'd need to add methods for converting to a `dyn Sequence`. It also might be confusing since we wouldn't add a `ReflectRef::Sequence` or anything like that. Is such a trait worth adding (either in this PR or a followup one)?

---

- Removed `Array` as supertrait of `List`
  - Added methods to `List` that were previously provided by `Array`

The `List` trait is no longer dependent on `Array`. Implementors of `List` can remove the `Array` impl and move its methods into the `List` impl (with only a couple tweaks).

```rust
// BEFORE
impl Array for Foo {
  fn get(&self, index: usize) -> Option<&dyn Reflect> {/* ... */}
  fn get_mut(&mut self, index: usize) -> Option<&mut dyn Reflect> {/* ... */}
  fn len(&self) -> usize {/* ... */}
  fn is_empty(&self) -> bool {/* ... */}
  fn iter(&self) -> ArrayIter {/* ... */}
  fn drain(self: Box<Self>) -> Vec<Box<dyn Reflect>> {/* ... */}
  fn clone_dynamic(&self) -> DynamicArray {/* ... */}
}

impl List for Foo {
  fn insert(&mut self, index: usize, element: Box<dyn Reflect>) {/* ... */}
  fn remove(&mut self, index: usize) -> Box<dyn Reflect> {/* ... */}
  fn push(&mut self, value: Box<dyn Reflect>) {/* ... */}
  fn pop(&mut self) -> Option<Box<dyn Reflect>> {/* ... */}
  fn clone_dynamic(&self) -> DynamicList {/* ... */}
}

// AFTER
impl List for Foo {
  fn get(&self, index: usize) -> Option<&dyn Reflect> {/* ... */}
  fn get_mut(&mut self, index: usize) -> Option<&mut dyn Reflect> {/* ... */}
  fn insert(&mut self, index: usize, element: Box<dyn Reflect>) {/* ... */}
  fn remove(&mut self, index: usize) -> Box<dyn Reflect> {/* ... */}
  fn push(&mut self, value: Box<dyn Reflect>) {/* ... */}
  fn pop(&mut self) -> Option<Box<dyn Reflect>> {/* ... */}
  fn len(&self) -> usize {/* ... */}
  fn is_empty(&self) -> bool {/* ... */}
  fn iter(&self) -> ListIter {/* ... */}
  fn drain(self: Box<Self>) -> Vec<Box<dyn Reflect>> {/* ... */}
  fn clone_dynamic(&self) -> DynamicList {/* ... */}
}
```

Some other small tweaks that will need to be made include:
- Use `ListIter` for `List::iter` instead of `ArrayIter` (the return type from `Array::iter`)
- Replace `array_hash` with `list_hash` in `Reflect::reflect_hash` for implementors of `List`
myreprise1 pushed a commit to myreprise1/bevy that referenced this issue Feb 15, 2023
Resolves bevyengine#7121

Decouples `List` and `Array` by removing `Array` as a supertrait of `List`. Additionally, similar methods from `Array` have been added to `List` so that their usages can remain largely unchanged.

My guess for why we originally made `List` a subtrait of `Array` is that they share a lot of common operations. We could potentially move these overlapping methods to a `Sequence` (name taken from bevyengine#7059) trait and make that a supertrait of both. This would allow functions to contain logic that simply operates on a sequence rather than "list vs array".

However, this means that we'd need to add methods for converting to a `dyn Sequence`. It also might be confusing since we wouldn't add a `ReflectRef::Sequence` or anything like that. Is such a trait worth adding (either in this PR or a followup one)?

---

- Removed `Array` as supertrait of `List`
  - Added methods to `List` that were previously provided by `Array`

The `List` trait is no longer dependent on `Array`. Implementors of `List` can remove the `Array` impl and move its methods into the `List` impl (with only a couple tweaks).

```rust
// BEFORE
impl Array for Foo {
  fn get(&self, index: usize) -> Option<&dyn Reflect> {/* ... */}
  fn get_mut(&mut self, index: usize) -> Option<&mut dyn Reflect> {/* ... */}
  fn len(&self) -> usize {/* ... */}
  fn is_empty(&self) -> bool {/* ... */}
  fn iter(&self) -> ArrayIter {/* ... */}
  fn drain(self: Box<Self>) -> Vec<Box<dyn Reflect>> {/* ... */}
  fn clone_dynamic(&self) -> DynamicArray {/* ... */}
}

impl List for Foo {
  fn insert(&mut self, index: usize, element: Box<dyn Reflect>) {/* ... */}
  fn remove(&mut self, index: usize) -> Box<dyn Reflect> {/* ... */}
  fn push(&mut self, value: Box<dyn Reflect>) {/* ... */}
  fn pop(&mut self) -> Option<Box<dyn Reflect>> {/* ... */}
  fn clone_dynamic(&self) -> DynamicList {/* ... */}
}

// AFTER
impl List for Foo {
  fn get(&self, index: usize) -> Option<&dyn Reflect> {/* ... */}
  fn get_mut(&mut self, index: usize) -> Option<&mut dyn Reflect> {/* ... */}
  fn insert(&mut self, index: usize, element: Box<dyn Reflect>) {/* ... */}
  fn remove(&mut self, index: usize) -> Box<dyn Reflect> {/* ... */}
  fn push(&mut self, value: Box<dyn Reflect>) {/* ... */}
  fn pop(&mut self) -> Option<Box<dyn Reflect>> {/* ... */}
  fn len(&self) -> usize {/* ... */}
  fn is_empty(&self) -> bool {/* ... */}
  fn iter(&self) -> ListIter {/* ... */}
  fn drain(self: Box<Self>) -> Vec<Box<dyn Reflect>> {/* ... */}
  fn clone_dynamic(&self) -> DynamicList {/* ... */}
}
```

Some other small tweaks that will need to be made include:
- Use `ListIter` for `List::iter` instead of `ArrayIter` (the return type from `Array::iter`)
- Replace `array_hash` with `list_hash` in `Reflect::reflect_hash` for implementors of `List`
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
A-Reflection Runtime information about types C-Usability A targeted quality-of-life change that makes Bevy easier to use S-Ready-For-Implementation This issue is ready for an implementation PR. Go for it!
Projects
Status: Done
Development

Successfully merging a pull request may close this issue.

4 participants