Returns a value referencing data owned by the current function!

I read couple of questions in the forum having same subject, but could not get it solved, I also read about Cow but couldn't know how to use it :frowning:

I know I can get read of all this headache by using String but want to learn more about borrowing and lifetime.

use std::collections::VecDeque;
use std::path::Path;
use serde::{Deserialize};
use std::borrow::*;

#[derive(Debug, Default, Deserialize)] struct NameTemp  { first: String, last: String }
#[derive(Debug, Clone)] struct Name<'a> { first: &'a str, last: &'a str }
#[derive(Debug, Clone, Default)] struct Names<'a> { names: VecDeque<Name<'a>> }

impl<'a> Names<'a> {
    pub(crate) fn new() -> Self { Default::default() }

    pub(crate) fn read(file: &'a str) -> Self {
        let mut records: Vec<NameTemp> = Vec::new();
        let mut list = Names::new();
        let file_path = Path::new(file);
        let mut rdr_ins = csv::ReaderBuilder::new()
            .has_headers(true)
            .delimiter(b',')
            .from_path(file_path).unwrap();
            for result in rdr_ins.records() {
                records.push(result.unwrap().deserialize(None).unwrap());
            };
            for record in &records {
                let name = Name {
                    first: record.first.as_str(),
                    last: record.last.as_str()
                };
                list.names.push_back(name);
            };
        
        // Here the error: returns a value referencing data owned by the current function
        list
    }
}

fn main(){
     // let mut names_list = Names::new();  names_list.read("src/names.csv");
    let names_list = Names::read("src/names.csv"); 
    println!("names list: {:#?}", names_list.names);
}

but got stuck that list kept owned by the function read and refused to be returned back to as an out put, i got this error:

  error[E0515]: cannot return value referencing local variable `records`
  --> src\main.rs:43:9
   |
32 |             for record in &records {
   |                           -------- `records` is borrowed here
...
43 |         list
   |         ^^^^ returns a value referencing data owned by the current function

You've created records here:

let mut records: Vec<NameTemp> = Vec::new();

and because it's a variable, and you're not storing it anywhere, it will be destroyed at the end of its scope. All records will be gone at the end of this function.

So you can't return references to records that are gone. And you can't return both owned struct and a temporary view of that struct together due to self-referential struct limitation of the borrow checker.

Again, don't put temporary scope-limited references in structs that you want to keep and use outside of a single scope.

<'a> is a huge warning sign. It's not for general-purpose structs, but a very limited case of a thin temporary view of another struct. Read it as "it will be unusable outside of its scope, can't be returned beyond that scope, can't exist without an owned counterpart that has been created before it's been created, and will remain alive for longer than that struct". If you don't intend to put all these limitations on a struct, don't make it temporary. Use owned types: String, Arc, Vec.

2 Likes

Is there a way to save it somewhere?

You can ask the caller for a place to store it. Note the 'a is not needed on file, as you don't store the name in the struct.

fn read(file: &str, records: &'a mut Vec<NameTemp>) -> Self {
    let mut list = Names::new();
    let file_path = Path::new(file);
    let mut rdr_ins = csv::ReaderBuilder::new()
        .has_headers(true)
        .delimiter(b',')
        .from_path(file_path)
        .unwrap();
    for result in rdr_ins.records() {
        records.push(result.unwrap().deserialize(None).unwrap());
    }
    for record in &*records {
        let name = Name {
            first: record.first.as_str(),
            last: record.last.as_str(),
        };
        list.names.push_back(name);
    }

    list
}

Of course, this means the caller has to provide the vector.

fn main() {
    let mut records = Vec::new();
    let names_list = Names::read("src/names.csv", &mut records);
    println!("names list: {:#?}", names_list.names);
}

Additionally it is not possible to modify records while names_list still exists, because names_list contains references into it, which may not be invalidated.

1 Like

Not really. In that function as written, you don't have access to any place to store it.

Generally to return a reference, you must first receive a reference as a function argument.

1 Like

so, it is more or less same like callback in JavaScript

Sorta, but it's a stretch.

2 Likes

I tried to summarize my understanding as below, hope there is no big misunderstanding:

  1. Caller create a variable to hold the records
    let mut records = Vec::new();

  2. Caller pass the reference & of this variable as mutable mut to the function
    let names_list = Names::read(..., &mut records);

  3. Function received the reference of the variable as mutable input
    fn read(..., records: &'a mut Vec<NameTemp>) -> ... { ... }

  4. Records are pushing into this variable
    records.push(....);

  5. Each record is read using &*records the objective of using &* here is to access the elements inside the records, as records is the reference of the vector, so we need first to do dereferencing so we can access the elements of the elements using * then pointing to each element using &

  6. We add the new record element (which is existing in the main function, but we updated it through referencing in the read function) to the list that is created inside the read function
    list.names.push_back(...);

  7. The output is returned to the main function, the list in the read function is dropped, bot records and names_list are updated, where records is the one that is holding the data and names_list is the one that is holding the view of this data

  8. records can not be modified while names_list due to single ownership restriction

Callback is a misleading way to think about this. It's all about scopes. You've had this:

{
    
    call read
    {
        create records
        return list
        destroy records
    }
    use list of records
}

and with the &mut Vec argument it's:

{
    create records
    call read
    {
        return list
    }
    use list of records
    destroy records
}

So it's about moving scope of records one level higher, so that it outlives the list. The list is still tied to it, and still can't be returned beyond the scope of records, but at least the scope is a bit larger.

5 Likes

This topic was automatically closed 90 days after the last reply. New replies are no longer allowed.