0

Commonly, a program object will contain a list of objects that reference a shared property in the container object.

Rust, without using a heap allocated reference counter or similar, prevents multiple owners to the same reference.

Example

The following code demonstrates an example pattern commonly encountered in the wild:

#[derive(Debug)]
struct Identity {
    token: String,
}

#[derive(Debug)]
struct Service<'a> {
    identity: &'a Identity,
    uri: String,
}

impl<'a> Service<'a> {
    pub fn new(identity: &'a Identity, uri: String) -> Self {
        Service { identity, uri }
    }
}

#[derive(Debug)]
struct Services<'a> {
    identity: Identity,
    services: Vec<Service<'a>>,
}

impl<'a> Services<'a> {
    pub fn new() -> Self {
        Services {
            identity: Identity {
                token: String::new(),
            },
            services: Vec::new(),
        }
    }

    pub fn add_service(&'a mut self, uri: String) {
        self.services.push(Service {
            identity: &self.identity,
            uri,
        });
    }
}

fn main() {
    let mut services = Services::new();
    services.add_service(String::from("https://api.stackexchange.com"));
    services.add_service(String::from("https://api.stackoverflow.com"));
    println!("{:?}", services);
}

Output

error[E0499]: cannot borrow `services` as mutable more than once at a time
  --> src/main.rs:45:5
   |
44 |     services.add_service(String::from("https://api.stackexchange.com"));
   |     -------- first mutable borrow occurs here
45 |     services.add_service(String::from("https://api.stackoverflow.com"));
   |     ^^^^^^^^
   |     |
   |     second mutable borrow occurs here
   |     first borrow later used here

error[E0502]: cannot borrow `services` as immutable because it is also borrowed as mutable
  --> src/main.rs:46:22
   |
44 |     services.add_service(String::from("https://api.stackexchange.com"));
   |     -------- mutable borrow occurs here
45 |     services.add_service(String::from("https://api.stackoverflow.com"));
46 |     println!("{:?}", services);
   |                      ^^^^^^^^
   |                      |
   |                      immutable borrow occurs here
   |                      mutable borrow later used here

Question

How can you create this pattern in Rust with the expectation that the Services vector will be updated within a GUI REPL loop?

Shepmaster
  • 388,571
  • 95
  • 1,107
  • 1,366
webish
  • 701
  • 2
  • 9
  • 17
  • 2
    TL;DR the duplicate — you don't, generally. Use an `Rc` or an `Arc` (like all the languages where this is a common pattern effectively do); pass in the reference instead of self-referencing; or in very rare cases, use unsafe code and prove that the code is safe. Your case _is not memory safe_, so I would not recommend it. – Shepmaster Dec 01 '20 at 18:01
  • "pass in the reference instead of self-referencing", not sure what this means or how to implement this... the use-case for me was to create an adapter and trait for a type of network connection involving devices but to abstract the specific implementation details away since the library I'm using makes its own decisions about how to manage the session and use that for each device. it seems like the prescribed solution is always punt these objects up to main func to establish a global scope that lacks generic application... – webish Dec 01 '20 at 19:00
  • also, looking at the example in the link provided... the reference to parent is made then parent is moved, I don't think my example does that. when a new service is added it takes a reference to the identity already existing on the Services struct which has a lifetime as long as any field it contains – webish Dec 01 '20 at 19:07
  • *not sure what this means or how to implement this* — [example](https://play.rust-lang.org/?version=stable&mode=debug&edition=2018&gist=70e3c3513b82196a4eeb17290f6b6d60). *then parent is moved, I don't think my example does that* — Since your code wishes to never move the parent, it shouldn't be a pain to declare the `Identity` immediately before and pass in the reference. – Shepmaster Dec 01 '20 at 19:27
  • 1
    Your example code is actually closer to the section in the answer titled "A type with a reference to itself". – Shepmaster Dec 01 '20 at 19:33
  • thanks for the example and that clarifies the suggestion but leaves the original encapsulation of a third-party network crate still a problem. the crate im using requires its own session and adapter object which I'd like to abstract away from the main function usage but that's not possible if the library's objects are created/managed in the main function and then passed to my adapter struct in a functional-like manner... I can see the main function becoming a dumping ground for all objects to keep in scope – webish Dec 01 '20 at 19:35
  • Realistically, cloning the `String` will probably never even appear on your profiling results. If it does, then [use an `Rc` or `Arc`](https://play.rust-lang.org/?version=stable&mode=debug&edition=2018&gist=efcde584ab73cc3696d4fe548d1894b0). – Shepmaster Dec 01 '20 at 19:38
  • 1
    *the crate im using requires its own session and adapter object* — yes, and that's a design issue with the library, unfortunately. See also [How to store rusqlite Connection and Statement objects in the same struct in Rust?](https://stackoverflow.com/q/32209391/155423); [How to store SQLite prepared statements for later?](https://stackoverflow.com/q/27552670/155423). Some libraries provide owned and reference versions, others have caching mechanisms, others have very lightweight structs that you can create over and over without saving them, etc. – Shepmaster Dec 01 '20 at 19:41
  • ok, i'm quickly finding out some features of rust programming will require structuring applications differently than i've done with other languages / platforms. – webish Dec 01 '20 at 22:10
  • Indeed. Note that you can treat a garbage collected language as a bunch of `Arc>` as a first approximation. There’s nothing wrong with using those tools in Rust. – Shepmaster Dec 01 '20 at 22:28

0 Answers0