Communities

Writing
Writing
Codidact Meta
Codidact Meta
The Great Outdoors
The Great Outdoors
Photography & Video
Photography & Video
Scientific Speculation
Scientific Speculation
Cooking
Cooking
Electrical Engineering
Electrical Engineering
Judaism
Judaism
Languages & Linguistics
Languages & Linguistics
Software Development
Software Development
Mathematics
Mathematics
Christianity
Christianity
Code Golf
Code Golf
Music
Music
Physics
Physics
Linux Systems
Linux Systems
Power Users
Power Users
Tabletop RPGs
Tabletop RPGs
Community Proposals
Community Proposals
tag:snake search within a tag
answers:0 unanswered questions
user:xxxx search by author id
score:0.5 posts with 0.5+ score
"snake oil" exact phrase
votes:4 posts with 4+ votes
created:<1w created < 1 week ago
post_type:xxxx type of post
Search help
Notifications
Mark all as read See all your notifications »
Q&A

Welcome to Software Development on Codidact!

Will you help us build our independent community of developers helping developers? We're small and trying to grow. We welcome questions about all aspects of software development, from design to code to QA and more. Got questions? Got answers? Got code you'd like someone to review? Please join us.

Post History

66%
+2 −0
Q&A How can I create and modify a struct over iterations of a loop?

tl;dr: Use current_section.as_mut().unwrap() instead of just current_section.unwrap(). Ownership is a core language-supported concept in Rust and this means what might be a single method in most l...

posted 3mo ago by Derek Elkins‭  ·  edited 3mo ago by Derek Elkins‭

Answer
#2: Post edited by user avatar Derek Elkins‭ · 2024-07-29T06:21:11Z (3 months ago)
typos
  • **tl;dr**: Use `current_section.as_mut().unwrap()` instead of just `current_section.unwrap()`.
  • Ownership is a core language-supported concept in Rust and this means what might be a single method in most languages can multiply into several that differ in their behavior with respect to ownership.You can see this in the diagram [here](https://x.com/iquilezles/status/1723798602932519265) for C++ which has similar ownership concerns albeit with far less support from the language itself.
  • Unlike C++, the Rust type system makes this far more evident and statically checked instead of just need to know the semantics for various types. In this case, we can see the issue simply by looking at the type of [`unwrap`](https://doc.rust-lang.org/std/option/enum.Option.html#method.unwrap). It's type is `pub fn unwrap(self) -> T`. We can see from this that `unwrap` consumes (takes ownership of) the object and it gives the caller ownership of the contained value it returns. For an `Option<Section>`, `T = Section` and thus the result of `unwrap` will be a `Section` and it would be dropped at the end of the block meaning the next iteration would be working with deallocated memory. Or it would if Rust let you run the code.
  • The solution in this case is to use [`as_mut`](https://doc.rust-lang.org/std/option/enum.Option.html#method.as_mut), as in `current_section.as_mut().unwrap().add_entry(line)`. `as_mut` has type `pub fn as_mut(&mut self) -> Option<&mut T>`. Again, the types communicate the relevant ownership information. We see that `as_mut` only mutable borrows the object its called on, instead of consuming it, and it returns a (mutable) reference. We can also see that the `Option` is being created and given ownership to the caller. *Now* when we invoke `unwrap`, we're invoking it with `T = &mut Section`. We're still consuming the `Option` which `as_mut` just created, but now we can tell from the type that the result of `unwrap` is just a mutable reference and that we have *not* taken ownership of the `Section`. We do own the mutable reference, but we don't own what it's referencing. The mutable reference dies immediately after the `add_entry`, which is good because we can only have one mutable reference to an object at a time.
  • ----
  • The following is more of a case study on lifetimes and how they affect Rust API design. It's not specific to your problem.
  • The [`entry`](https://doc.rust-lang.org/std/collections/struct.HashMap.html#method.entry) for `HashMap` and the associated [`Entry`](https://doc.rust-lang.org/std/collections/hash_map/enum.Entry.html) type is a good illustration of these ideas and how they impact interface design in Rust and are worth studying. This pattern of needing to introduce auxiliary data types to capture life-times appropriately is common in Rust library design. Indeed, earlier versions of Rust had more stuff built into the language until people realized that it could be accomplished without additional language features.
  • On its face, `entry` just seems like an efficient way to get at a (potential) location in the data type. If the key is in the hash map, we get an [`OccupiedEntry`](https://doc.rust-lang.org/std/collections/hash_map/struct.OccupiedEntry.html) that we can access and update in-place much like [`get_mut`](https://doc.rust-lang.org/std/collections/struct.HashMap.html#method.get_mut). If the key isn't in the hash map, we get a [`VacantEntry`](https://doc.rust-lang.org/std/collections/hash_map/struct.VacantEntry.html) which we can update to insert an element into the hash map. This is nice as for many collections the work to look up a value is similar to the work needed to insert the value. With this type, we don't need to do that work twice. The alternative would be to use `get_mut` and `insert` which would likely do that work twice.
  • Except... as I found out, there are cases where that two step approach (`get_mut`, `insert`) doesn't really work. Recently, I was making a trie type which is roughly a node which is, recursively, a hash map of child nodes. To insert an value into the trie, I had a mutable "current node" variable whose type was a mutable reference to a node which would be updated in a loop as I traversed the trie. I originally wrote this with a `get_mut`-`insert` approach, but this doesn't work because the `get_mut` is still mutably borrowing the parent node when you go to try to `insert`. I couldn't figure out any reasonable way to make the borrow checker happy. Maybe I could have done something with a flag to indicate to insert later, but that would be really ugly and inefficient. Searching the methods of `HashMap` was when I found `entry` which solves this problem elegantly. With `entry` I didn't need to attempt to mutable borrow the parent node again; I could just insert via the `VacantEntry` directly.
  • As a final example of multiple methods that differ solely in their ownership behavior and how Rust's types indicate this, the `OccupiedEntry` type has two very similar methods: [`get_mut`](https://doc.rust-lang.org/std/collections/hash_map/struct.OccupiedEntry.html#method.get_mut) and [`into_mut`](https://doc.rust-lang.org/std/collections/hash_map/struct.OccupiedEntry.html#method.into_mut). Their types are `pub fn get_mut(&mut self) -> &mut V` and `pub fn into_mut(self) -> &'a mut V`. We see from the types that `get_mut` does not consume the `OccupiedEntry` and the mutable reference it returns only lives as long as the `OccupiedEntry`. By contrast, `into_mut` consumes the `OccupiedEntry` and returns a mutable reference with a different lifetime (necessarily since the object `into_mut` is called on doesn't live passed the call to `into_mut`) which, in this case, is associated to the lifetime of the `HashMap`. For my trie use-case, I needed to use `into_mut` since my current node variable lived across iterations of a loop, while the `OccupiedEntry` had a very brief life in a branch of a `match`.
  • **tl;dr**: Use `current_section.as_mut().unwrap()` instead of just `current_section.unwrap()`.
  • Ownership is a core language-supported concept in Rust and this means what might be a single method in most languages can multiply into several that differ in their behavior with respect to ownership.You can see this in the diagram [here](https://x.com/iquilezles/status/1723798602932519265) for C++ which has similar ownership concerns albeit with far less support from the language itself.
  • Unlike C++, the Rust type system makes this far more evident and statically checked instead of just requiring you to know the semantics for various types. In this case, we can see the issue simply by looking at the type of [`unwrap`](https://doc.rust-lang.org/std/option/enum.Option.html#method.unwrap). Its type is `pub fn unwrap(self) -> T`. We can see from this that `unwrap` consumes (takes ownership of) the object it's called on, and it gives the caller ownership of the contained value it returns. For an `Option<Section>`, `T = Section` and thus the result of `unwrap` will be a `Section` which would be dropped at the end of the block meaning the next iteration would be working with deallocated memory. Or it would if Rust let you run the code.
  • The solution in this case is to use [`as_mut`](https://doc.rust-lang.org/std/option/enum.Option.html#method.as_mut), as in `current_section.as_mut().unwrap().add_entry(line)`. `as_mut` has type `pub fn as_mut(&mut self) -> Option<&mut T>`. Again, the types communicate the relevant ownership information. We see that `as_mut` only mutably borrows the object it's called on, instead of consuming it, and it returns a (mutable) reference. We can also see that the `Option` is being created and given ownership to the caller. *Now* when we invoke `unwrap`, we're invoking it with `T = &mut Section`. We're still consuming the `Option` which `as_mut` just created, but now we can tell from the type that the result of `unwrap` is just a mutable reference and that we have *not* taken ownership of the `Section`. We do own the mutable reference, but we don't own what it's referencing. The mutable reference dies immediately after the `add_entry`, which is good because we can only have one mutable reference to an object at a time.
  • ----
  • The following is more of a case study on lifetimes and how they affect Rust API design. It's not specific to your problem.
  • The [`entry`](https://doc.rust-lang.org/std/collections/struct.HashMap.html#method.entry) method for `HashMap` and the associated [`Entry`](https://doc.rust-lang.org/std/collections/hash_map/enum.Entry.html) type is a good illustration of these ideas and how they impact interface design in Rust and are worth studying. This pattern of needing to introduce auxiliary data types to capture life-times appropriately is common in Rust library design. Indeed, earlier versions of Rust had more stuff built into the language until people realized that it could be accomplished without additional language features.
  • On its face, `entry` just seems like an efficient way to get at a (potential) location in the collection. If the key is in the hash map, we get an [`OccupiedEntry`](https://doc.rust-lang.org/std/collections/hash_map/struct.OccupiedEntry.html) that we can access and update in-place much like [`get_mut`](https://doc.rust-lang.org/std/collections/struct.HashMap.html#method.get_mut). If the key isn't in the hash map, we get a [`VacantEntry`](https://doc.rust-lang.org/std/collections/hash_map/struct.VacantEntry.html) which we can update to insert an element into the hash map. This is nice as for many collections the work to look up a value is similar to the work needed to insert the value. With this type, we don't need to do that work twice. The alternative would be to use `get_mut` and `insert` which would likely do that work twice.
  • Except... as I found out, there are cases where that two step approach (`get_mut`, `insert`) doesn't really work. Recently, I was making a trie type which is roughly a node which is, recursively, a hash map of child nodes. To insert an value into the trie, I had a mutable "current node" variable whose type was a mutable reference to a node which would be updated in a loop as I traversed the trie. I originally wrote this with a `get_mut`-`insert` approach, but this doesn't work because the `get_mut` is still mutably borrowing the parent node when you go to try to `insert`. I couldn't figure out any reasonable way to make the borrow checker happy. Maybe I could have done something with a flag to indicate to insert later, but that would be really ugly and inefficient. Searching the methods of `HashMap` was when I found `entry` which solves this problem elegantly. With `entry` I didn't need to attempt to mutably borrow the parent node again; I could just insert via the `VacantEntry` directly.
  • As a final example of multiple methods that differ solely in their ownership behavior and how Rust's types indicate this, the `OccupiedEntry` type has two very similar methods: [`get_mut`](https://doc.rust-lang.org/std/collections/hash_map/struct.OccupiedEntry.html#method.get_mut) and [`into_mut`](https://doc.rust-lang.org/std/collections/hash_map/struct.OccupiedEntry.html#method.into_mut). Their types are `pub fn get_mut(&mut self) -> &mut V` and `pub fn into_mut(self) -> &'a mut V`. We see from the types that `get_mut` does not consume the `OccupiedEntry` and the mutable reference it returns only lives as long as the `OccupiedEntry`. By contrast, `into_mut` consumes the `OccupiedEntry` and returns a mutable reference with a different lifetime (necessarily since the object `into_mut` is called on doesn't live passed the call to `into_mut`) which, in this case, is associated to the lifetime of the `HashMap`. For my trie use-case, I needed to use `into_mut` since my current node variable lived across iterations of a loop, while the `OccupiedEntry` had a very brief life in a branch of a `match`.
#1: Initial revision by user avatar Derek Elkins‭ · 2024-07-29T06:14:13Z (3 months ago)
**tl;dr**: Use `current_section.as_mut().unwrap()` instead of just `current_section.unwrap()`.

Ownership is a core language-supported concept in Rust and this means what might be a single method in most languages can multiply into several that differ in their behavior with respect to ownership.You can see this in the diagram [here](https://x.com/iquilezles/status/1723798602932519265) for C++ which has similar ownership concerns albeit with far less support from the language itself.

Unlike C++, the Rust type system makes this far more evident and statically checked instead of just need to know the semantics for various types. In this case, we can see the issue simply by looking at the type of [`unwrap`](https://doc.rust-lang.org/std/option/enum.Option.html#method.unwrap). It's type is `pub fn unwrap(self) -> T`. We can see from this that `unwrap` consumes (takes ownership of) the object and it gives the caller ownership of the contained value it returns. For an `Option<Section>`, `T = Section` and thus the result of `unwrap` will be a `Section` and it would be dropped at the end of the block meaning the next iteration would be working with deallocated memory. Or it would if Rust let you run the code.

The solution in this case is to use [`as_mut`](https://doc.rust-lang.org/std/option/enum.Option.html#method.as_mut), as in `current_section.as_mut().unwrap().add_entry(line)`. `as_mut` has type `pub fn as_mut(&mut self) -> Option<&mut T>`. Again, the types communicate the relevant ownership information. We see that `as_mut` only mutable borrows the object its called on, instead of consuming it, and it returns a (mutable) reference. We can also see that the `Option` is being created and given ownership to the caller. *Now* when we invoke `unwrap`, we're invoking it with `T = &mut Section`. We're still consuming the `Option` which `as_mut` just created, but now we can tell from the type that the result of `unwrap` is just a mutable reference and that we have *not* taken ownership of the `Section`. We do own the mutable reference, but we don't own what it's referencing. The mutable reference dies immediately after the `add_entry`, which is good because we can only have one mutable reference to an object at a time.

----

The following is more of a case study on lifetimes and how they affect Rust API design. It's not specific to your problem.

The [`entry`](https://doc.rust-lang.org/std/collections/struct.HashMap.html#method.entry) for `HashMap` and the associated [`Entry`](https://doc.rust-lang.org/std/collections/hash_map/enum.Entry.html) type is a good illustration of these ideas and how they impact interface design in Rust and are worth studying. This pattern of needing to introduce auxiliary data types to capture life-times appropriately is common in Rust library design. Indeed, earlier versions of Rust had more stuff built into the language until people realized that it could be accomplished without additional language features.

On its face, `entry` just seems like an efficient way to get at a (potential) location in the data type. If the key is in the hash map, we get an [`OccupiedEntry`](https://doc.rust-lang.org/std/collections/hash_map/struct.OccupiedEntry.html) that we can access and update in-place much like [`get_mut`](https://doc.rust-lang.org/std/collections/struct.HashMap.html#method.get_mut). If the key isn't in the hash map, we get a [`VacantEntry`](https://doc.rust-lang.org/std/collections/hash_map/struct.VacantEntry.html) which we can update to insert an element into the hash map. This is nice as for many collections the work to look up a value is similar to the work needed to insert the value. With this type, we don't need to do that work twice. The alternative would be to use `get_mut` and `insert` which would likely do that work twice.

Except... as I found out, there are cases where that two step approach (`get_mut`, `insert`) doesn't really work. Recently, I was making a trie type which is roughly a node which is, recursively, a hash map of child nodes. To insert an value into the trie, I had a mutable "current node" variable whose type was a mutable reference to a node which would be updated in a loop as I traversed the trie. I originally wrote this with a `get_mut`-`insert` approach, but this doesn't work because the `get_mut` is still mutably borrowing the parent node when you go to try to `insert`. I couldn't figure out any reasonable way to make the borrow checker happy. Maybe I could have done something with a flag to indicate to insert later, but that would be really ugly and inefficient. Searching the methods of `HashMap` was when I found `entry` which solves this problem elegantly. With `entry` I didn't need to attempt to mutable borrow the parent node again; I could just insert via the `VacantEntry` directly.

As a final example of multiple methods that differ solely in their ownership behavior and how Rust's types indicate this, the `OccupiedEntry` type has two very similar methods: [`get_mut`](https://doc.rust-lang.org/std/collections/hash_map/struct.OccupiedEntry.html#method.get_mut) and [`into_mut`](https://doc.rust-lang.org/std/collections/hash_map/struct.OccupiedEntry.html#method.into_mut). Their types are `pub fn get_mut(&mut self) -> &mut V` and `pub fn into_mut(self) -> &'a mut V`. We see from the types that `get_mut` does not consume the `OccupiedEntry` and the mutable reference it returns only lives as long as the `OccupiedEntry`. By contrast, `into_mut` consumes the `OccupiedEntry` and returns a mutable reference with a different lifetime (necessarily since the object `into_mut` is called on doesn't live passed the call to `into_mut`) which, in this case, is associated to the lifetime of the `HashMap`. For my trie use-case, I needed to use `into_mut` since my current node variable lived across iterations of a loop, while the `OccupiedEntry` had a very brief life in a branch of a `match`.