{"action":"created","issue":{"url":"https://api.github.com/repos/slide-rs/specs/issues/519","repository_url":"https://api.github.com/repos/slide-rs/specs","labels_url":"https://api.github.com/repos/slide-rs/specs/issues/519/labels{/name}","comments_url":"https://api.github.com/repos/slide-rs/specs/issues/519/comments","events_url":"https://api.github.com/repos/slide-rs/specs/issues/519/events","html_url":"https://github.com/slide-rs/specs/issues/519","id":390315530,"node_id":"MDU6SXNzdWUzOTAzMTU1MzA=","number":519,"title":"Lifetime issues with component storages","user":{"login":"sunjay","id":530939,"node_id":"MDQ6VXNlcjUzMDkzOQ==","avatar_url":"https://avatars2.githubusercontent.com/u/530939?v=4","gravatar_id":"","url":"https://api.github.com/users/sunjay","html_url":"https://github.com/sunjay","followers_url":"https://api.github.com/users/sunjay/followers","following_url":"https://api.github.com/users/sunjay/following{/other_user}","gists_url":"https://api.github.com/users/sunjay/gists{/gist_id}","starred_url":"https://api.github.com/users/sunjay/starred{/owner}{/repo}","subscriptions_url":"https://api.github.com/users/sunjay/subscriptions","organizations_url":"https://api.github.com/users/sunjay/orgs","repos_url":"https://api.github.com/users/sunjay/repos","events_url":"https://api.github.com/users/sunjay/events{/privacy}","received_events_url":"https://api.github.com/users/sunjay/received_events","type":"User","site_admin":false},"labels":[],"state":"open","locked":false,"assignee":null,"assignees":[],"milestone":null,"comments":5,"created_at":"2018-12-12T16:51:06Z","updated_at":"2019-01-03T15:42:48Z","closed_at":null,"author_association":"NONE","body":"I originally posted about this in [the users forum](https://users.rust-lang.org/t/returning-iterator-of-specs-components/23012?u=sunjay) and then on [Reddit](https://www.reddit.com/r/rust/comments/a45q5i/a_simple_agenda_for_the_rust_game_development_wg/ebkdwjq/?context=1).\r\n\r\n*Terminology Note:* When I say \"owned X\" in the text below, I mean that you have ownership of a value X of type `T`, not a reference to that value (e.g. `&T` or `&mut T`)\r\n\r\nYou can look at this issue from two perspectives:\r\n\r\n1. It is hard to get a `SystemData` containing references to storages.\r\n    Example: You can get `(ReadStorage<A>, ReadStorage<B>)` by calling the `system_data` method on `World` but not `(&ReadStorage<A>, &ReadStorage<B>)`\r\n2. It is not possible to use the `Join` trait with the owned versions of component storages.\r\n    Example: You can do `(&a, &b).join()` but not `(a, b).join()`\r\n\r\nBoth of these issues exist for good reasons, but they make code like the following very hard to write:\r\n\r\n```rust\r\nfn iter_components<'a>(world: &'a World) -> impl Iterator<Item=(&Position, &Velocity)> + 'a {\r\n    // Two *owned* storages allocated on the stack of this function\r\n    let (positions, velocities) = world.system_data::<(ReadStorage<Position>, ReadStorage<Velocity>)>();\r\n\r\n    // Returning a reference to stack allocated data (does not and should not work!)\r\n    (&positions, &velocities).join()\r\n}\r\n```\r\n\r\nThis doesn't work and you get the following error: (copied from users forum post, see that for the full code example)\r\n\r\n```\r\nerror[E0515]: cannot return value referencing local variable `velocities`       \r\n  --> src/main.rs:22:5                                                          \r\n   |                                                                            \r\n22 |     (&positions, &velocities).join()                                       \r\n   |     ^^^^^^^^^^^^^-----------^^^^^^^^                                       \r\n   |     |            |                                                         \r\n   |     |            `velocities` is borrowed here                             \r\n   |     returns a value referencing data owned by the current function         \r\n                                                                                \r\nerror[E0515]: cannot return value referencing local variable `positions`        \r\n  --> src/main.rs:22:5                                                          \r\n   |                                                                            \r\n22 |     (&positions, &velocities).join()                                       \r\n   |     ^----------^^^^^^^^^^^^^^^^^^^^^                                       \r\n   |     ||                                                                     \r\n   |     |`positions` is borrowed here                                          \r\n   |     returns a value referencing data owned by the current function\r\n```\r\n\r\nIf either of the two things I listed above weren't the case, this code could potentially compile:\r\n\r\n1. If you could get a system data instance containing references to storages: (the storages would have to be owned within the world or something)\r\n\r\n```rust\r\nfn iter_components<'a>(world: &'a World) -> impl Iterator<Item=(&Position, &Velocity)> + 'a {\r\n    // Two references to storages allocated somewhere by World\r\n    // Notice the `&` before the `ReadStorage` types here\r\n    let (positions, velocities) = world.system_data::<(&ReadStorage<Position>, &ReadStorage<Velocity>)>();\r\n\r\n    // positions and velocities are both references, so we can use them here no problem\r\n    (positions, velocities).join()\r\n}\r\n```\r\n\r\n2. If it were possible to use the `Join` trait on owned versions of the storages:\r\n\r\n```rust\r\nfn iter_components<'a>(world: &'a World) -> impl Iterator<Item=(&Position, &Velocity)> + 'a {\r\n    // Two *owned* storages allocated on the stack of this function\r\n    let (positions, velocities) = world.system_data::<(ReadStorage<Position>, ReadStorage<Velocity>)>();\r\n\r\n    // Storages are *moved* into this tuple that we call `join` on. This works!\r\n    (positions, velocities).join()\r\n}\r\n```\r\n\r\nA note about this: `ReadStorage` makes it pretty obvious that we are immutably borrowing here, but with `WriteStorage` we would probably need some methods like `read_owned()` and `write_owned()` to emulate what you could do with `&` and `&mut` before. See the following code for more details:\r\n\r\n```rust\r\nfn iter_components<'a>(world: &'a World) -> impl Iterator<Item=(&mut Position, &Velocity)> + 'a {\r\n    // Two *owned* storages allocated on the stack of this function\r\n    let (positions, velocities) = world.system_data::<(WriteStorage<Position>, WriteStorage<Velocity>)>();\r\n\r\n    // You can join over either of these storages in two modes:\r\n    // * `&positions` for read-only access to the components\r\n    // * `&mut positions` for read-write access to the components\r\n    // `write_owned()` could wrap the storage in a type that implements\r\n    // the `Join` trait and allows access to mutable references\r\n    // Instead of `read_owned()` we could default to the behaviour that\r\n    // the owned version of a storage only allows reading\r\n\r\n    // Storages are *moved* into this tuple that we call `join` on. This works!\r\n    (positions.write_owned(), velocities.read_owned()).join()\r\n}\r\n```\r\n\r\nI think this solution is probably the easiest to implement because it doesn't involve the `World` having to hold on to arbitrary storages for some extended amount of time.\r\n\r\nThe API I would suggest is:\r\n\r\n* `ReadStorage<T>` and `WriteStorage<T>` implement `Join` and produce `&T`\r\n* `WriteStorage<T>` provides a method (e.g. `write_owned()`) that returns an owned value that implements `Join` and produces `&mut T` (perhaps something similar to `MaybeJoin`?)\r\n\r\nThis may cause issues with `World` because it's probably not a good thing if a storage lives longer than expected. I am not familiar enough with the internals to comment on that."},"comment":{"url":"https://api.github.com/repos/slide-rs/specs/issues/comments/451181852","html_url":"https://github.com/slide-rs/specs/issues/519#issuecomment-451181852","issue_url":"https://api.github.com/repos/slide-rs/specs/issues/519","id":451181852,"node_id":"MDEyOklzc3VlQ29tbWVudDQ1MTE4MTg1Mg==","user":{"login":"torkleyy","id":23152057,"node_id":"MDQ6VXNlcjIzMTUyMDU3","avatar_url":"https://avatars1.githubusercontent.com/u/23152057?v=4","gravatar_id":"","url":"https://api.github.com/users/torkleyy","html_url":"https://github.com/torkleyy","followers_url":"https://api.github.com/users/torkleyy/followers","following_url":"https://api.github.com/users/torkleyy/following{/other_user}","gists_url":"https://api.github.com/users/torkleyy/gists{/gist_id}","starred_url":"https://api.github.com/users/torkleyy/starred{/owner}{/repo}","subscriptions_url":"https://api.github.com/users/torkleyy/subscriptions","organizations_url":"https://api.github.com/users/torkleyy/orgs","repos_url":"https://api.github.com/users/torkleyy/repos","events_url":"https://api.github.com/users/torkleyy/events{/privacy}","received_events_url":"https://api.github.com/users/torkleyy/received_events","type":"User","site_admin":false},"created_at":"2019-01-03T15:42:48Z","updated_at":"2019-01-03T15:42:48Z","author_association":"MEMBER","body":"Let's revisit this after #523 "}}
{"actor":{"display_login":"torkleyy"}}