Given a mutable instance of Person, how can I create another immutable instance whose name data is shared with the first one? Example:
// When orig goes out of scope/dropped/etc.,
// an error should be raised if copy is accessed
// OR a copy/clone of the name field be made.
let mut orig = Person{ name: String::from("Adam"), age: 32 }
let copy = Person{ name: orig.name, age: 64} // What to do?
orig.name = String::from("Alice");
assert_eq!(String::from("Alice"), copy.name);
I'm new to Rust and not very familiar with its ownership rules. My only option seems to be RefCell?
Sharing in Rust must be explicit, and will be visible in the types. There's no GC or any magic to handle sharing on arbitrary objects, so you can share only types that are explicitly shareable.
In this case you would have to define the field as name: Arc<String> to be able to share an immutable string between instances.
There's Arc<str> type that is slightly more efficient (saves a layer of indirection by storing string data in Arc inline), and Rc instead of Arc if you only share data within single thread.
Sharing in Rust automatically implies immutability, so you don't need to do anything extra to keep the data in Arc/Rc immutable. If you want mutable, then RefCell/Mutex are the way to bring mutability back in shared objects.
Thank you for your answer. Are there other alternatives at my disposal? Reference-counting seems to be ditching Rust's core strengths in favour of a more traditional GC-oriented paradigm. Do you think something like this involving lifetimes would work?
That won't do what you want. Someone has to own the data, unless all your names are known at compile time (in which case you can use &'static str).
So then you might think, well I could have two structs or a generic struct, so one owns and the other borrows. But that still won't do what you want. Despite the name, Rust lifetimes (those 'a things) generally represent the duration of a borrow. They do not represent the liveness of a variable. And the borrow checker gives your programs a pass-or-fail verdict, but cannot change the semantics of your program. Which means that lifetime annotations cannot change how long a variable is alive, for example.
Being borrowed conflicts with various uses, such as being overwritten, so trying to use references here will just lead to frustration.
Lifetime-carrying structs are usually for temporaries, not long-lived data structurse. While you're still in the stage of learning Rust where you need to ask if you should define one, the answer is usually no.
When the data needs to be shared and borrowing isn't suitable, you need some sort of shared ownership instead, and that's what Rc and Arc provide.
Arc is perfectly fine and aligned with Rust's way of handling data. It's a type that gives you both ownership and sharing, and that combination is often useful.
The technology for just freely sharing arbitrary pointers without reference counting or restrictions where they can be used is… a garbage collector. Rust famously doesn't have one. You can't always have pure compile-time alternatives.
Your change to &str added sharing, but also removed owning. The code now says that the name is not stored in this struct. It's not even allowed to be ever stored in (owned by) this struct. The struct is no longer a normal self-contained object, but has become a temporary view that will be very strictly bound to a scope of whatever place it borrowed the name from (usually a local variable). The scope is conservatively determined at compile time, and the compiler will never do anything to extend it.
Temporary references are great for sharing data with functions for the duration of a function call, for creating temporary views and throwaway indexes of datasets for use within a single function call/scope, for returning references to objects in collections for immediate use.
But for a struct like Person that will be returned from functions, stored and used for longer than a duration of a single function call, Rust references (temporary loans) are the wrong tool. Going that path without understanding how drastically different they are from referencing objects in other languages, will cause you a nightmare of randomly adding <'a> annotations everywhere and making compiler demand 'static lifetime that doesn't make sense and cannot be satisfied in a sensible way.
Sometimes reference counting are used for that. You have to take care with cycles and use rc::Weak to avoid leaks if you use reference counting for a general graph. Alternatively, some sort of indexing or key system can be used instead of a pointer type.
Graphs (especially if cyclic), linked-lists, and the like are less common in Rust than they are in some other languages, and in classical CS education. Especially in the sense of rolling your own, versus using an established crate.