How to go about using APIs with reference inputs

From time to time I encounter some crate functions and structs that accepts a reference and return something that holds this reference. I'm looking for good methods to accommodate the borrow checker in this case

For example lets use this simplistic example:

struct VecRef<'a> {
    v: &'a [u8],
}

fn create<'a>() -> VecRef<'a> {
    let v = vec![1, 2, 3]; // get vec from somewhere
    let r = VecRef { v: &v };
    r // error
}

fn run() {
    let v_ref = create();
}

Suppose that VecRef is an external struct that I have no access to.
The create methods gets Vec from somewhere, and wishes to construct a VecRef. This is of course not possible since v is dropped and the reference becomes invalid.

My first instinct was to return and hold v in addition to the ref r, but this would create a self referential construct which will make the borrow checker unhappy.

I've found some unsafe solutions that involves unsafe self reference + pinning, but I wonder if there is a safe technique to handle this.

P.S I know that moving the vec creation to the run method will solve this simple example, but in a complex program it's not always possible and easy

Why not pass a Vec to the create function?

1 Like

Again, and again, and again: a generic lifetime parameter will always have to be longer than anything in the callee lives for, since such a parameter is chosen by the caller, the scope of which is necessarily a strict superset of (hence, longer than) that of the callee.

Thus, you are never going to be able to usefully implement functions of the form

fn foo<'a>(/* nothing to get a lifetime from */) -> Foo<'a> {
    ...
}

The only ways to technically achieve this are:

  • leaking memory (needless to say why not practical)
  • returning a reference to a static (which could sometimes be useful but the API is still weird because you could just return an explicit &'static instead)
  • or using unsafe (which is not going to be correct, but it will "make it compile").

If you can't move the value to be borrowed outside the function, then you are not going to be able to do this safely and correctly.

You really shouldn't be forcing this pattern. It's not an accident that it's hard to implement. If your code and data has an adequate structure, then the tree- or at least DAG-structured ownership pattern is achievable in like 99.9% of the time. It's very rare that you have a legitimate reason for wanting cyclic dependencies; but if you do (e.g. you need to represent general graphs), then you can emulate that by putting all of your data into a flat array and referencing each item by its index. That's what general graph libraries do.

2 Likes

@H2CO3
Thanks for the answer. I understand the reasoning you describe, but still struggling with the "correct" structure.

Here's a bit more complex example. In this example we simulate a "server" that receives messages. the server constructs VecRef for future use. To do this it also have vec_storage to keep an owned copy of the data.

This attempt fails since in on_msg I must borrow self with the whole lifetime of server preventing me from using it again

struct Server<'a> {
    vec_storage: HashMap<String, Vec<u8>>,
    ref_storage: HashMap<String, VecRef<'a>>,
}

impl<'a> Server<'a> {
    fn on_msg(&'a mut self, id: String, vec: Vec<u8>) {
        let vec = self.vec_storage.entry(id.clone()).or_insert(vec);

        let r = VecRef { v: vec };
        self.ref_storage.insert(id, r);
    }

    fn access(&self, id: String) {
        let r = self.ref_storage.get(&id).unwrap();
        println!("len is {}", r.len());
    }
}

fn run() {
    let mut server = Server {
        vec_storage: HashMap::new(),
        ref_storage: HashMap::new(),
    };

    server.on_msg("test".into(), vec![1, 2]); 
    server.access("test".into()); // error
}

This results in a borrow checker error

   |
42 |     server.on_msg("test".into(), vec![1, 2]);
   |     ---------------------------------------- mutable borrow occurs here
43 |     server.access("test".into());
   |     ^^^^^^^^^^^^^^^^^^^^^^^^^^^^
   |     |
   |     immutable borrow occurs here
   |     mutable borrow later used here

If the library offers no owning-not-borrowing alternative to VecRef, that may be a problem with the library, not in your understanding. Sometimes library authors mistakenly assume this is all you need. It's a pattern that always works fine in trivial example programs where everything is in main(), so it can be possible to forget about use cases where that is not possible.

You are trying to create a self-referential structure. Don't do that. It's not possible to usefully achieve a self-referential type.

If you need to identify a subset of elements in a separate collection, just store their indices.

To be usefull in this case, the VecRef type should probably implement ToOwned. Then you can change the create function to:

fn create<'a>() -> T { // replace T with the return type of VecRef::to_owned()
    let v = vec![1, 2, 3]; // get vec from somewhere
    let r = VecRef { v: &v };
    r.to_owned()
}

For the VecRef example, T would probably just be Vec<u8>. For whatever borrowing structs you encounter in the wild, it may be a more specific type.

If the borrowing type you are trying to use don't implement ToOwned, it may be a good idea to submit a PR to that crate for implementing it.

@kaj @kpreid
I believe that in the case I've stumbled upon you are right. An issue and a PR have been opened in the crate I've been using to change the signature of said methods.

Thanks!

This topic was automatically closed 90 days after the last reply. We invite you to open a new topic if you have further questions or comments.