Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Are there any restrictions when using a reference in a field of a struct?

I am trying to understand how lifetimes in Rust affect Structs. Attached is a minimal example that confuses me.

I would like to give a struct a reference to an object and then later it is necessary to change this object. Since it is not possible to change the object while it is borrowed, I thought that I have to remove the reference while changing it. So my idea was to define the variable as an option and remove the reference during the modification of the object by setting it to None. For the example without the use of a Struct this seems to work. However, if I now put this reference into a Struct it does not work. It seems to me that the borrow checker overlooks the fact that the variable string is no longer borrowed. Is there a way to still achieve the desired behaviour with a Struct?

This works (link to playground):

fn main() {
    let mut string = "mutable string".to_string();
    let mut ref_to_string: Option<&str> = Some(&string);
    ref_to_string = None;
    string = "mutated string".to_string();
    ref_to_string = Some(&string);
}

However, this doesn't work link to playground:

struct Foo<'a> {
    pub ref_data: Option<&'a str>,
}

fn main() {
    let mut string = "mutable string".to_string();
    let mut foo = Foo{ref_data: Some(&string)};
    foo.ref_data = None;
    string = "mutated string".to_string();
    foo.ref_data = Some(&string);
}

Error message:

error[E0506]: cannot assign to `string` because it is borrowed
  --> src/main.rs:11:5
   |
9  |     let mut foo = Foo{ref_data: Some(&string)};
   |                                      ------- borrow of `string` occurs here
10 |     foo.ref_data = None;
11 |     string = "mutated string".to_string();
   |     ^^^^^^ assignment to borrowed `string` occurs here
12 |     foo.ref_data = Some(&string);
   |     ---------------------------- borrow later used here

For more information about this error, try `rustc --explain E0506`.
like image 206
hochej Avatar asked Oct 12 '21 13:10

hochej


People also ask

Is reference type applicable for structure?

In general, a struct should only contain a public and/or mutable field of a reference type if one of the following conditions applies: All instances of that type may be regarded as inherently immutable (as is the case of `string`)

Is int a struct in c#?

5, the simple types provided by C#, such as int , double , and bool , are, in fact, all struct types.

Can a struct be null in C?

3 answers. It is not possible to set a struct with NULL as it is declared. Fila f = NUll; error: invalid initializer. So cast% from% to NULL , which is not even a type in this code, or assign Fila to a primitive type variable is "wrong".


Video Answer


3 Answers

The problem is that you are referencing the string variable twice. When borrowing the first one is binding the &str lifetime to be alive at least as foo. There should be no problem to reference another string that lives in "another" variable:

fn main() {
    let string = "mutable string".to_string();
    let mut foo = Foo {
        ref_data: Some(&string),
    };
    foo.ref_data = None;

    let string = "mutated string".to_string();
    foo.ref_data = Some(&string);
}

Playground

Notice the use of another let binding instead of mutating the variable.

like image 137
Netwave Avatar answered Oct 18 '22 07:10

Netwave


The secret lies in the definition of the Foo struct:

struct Foo<'a> {
    pub ref_data: Option<&'a str>,
}

And more specifically in the 'a lifetime specifier. By specifying that Foo lives for 'a and that ref_data has the same lifetime (&'a str), you say that your &str will live as long as Foo. So as long as Foo is alive, compiler cannot drop the reference to your string.

Unfortunately, there is no way to specify that the struct has lifetime of 'a only while the field is Some.

To mutate an object refenced by a structure, you can:

  1. Transfer ownership of the object to the struct, and then get a mutable reference to it from the struct.
  2. Use internal mutability (see RefCell).
  3. Drop you struct before mutating and recreate it after mutation is finished.

Which approach is better depends on your specific use case.

like image 2
Maxim Gritsenko Avatar answered Oct 18 '22 07:10

Maxim Gritsenko


The problem is that the whole Foo is generic over a lifetime. That way, when you assigned foo = Foo {ref_data: Some(&string)}, the type of foo is Foo<'lifetime_of_string>. You can set foo.ref_data to None all you want, but as far as borrow checking is concerned, there is still a live object whose lifetime overlaps that of string. For this reason, string is borrowed as long as foo lives. The only way to do it is to drop the whole foo:

let mut string = "mutable string".to_string();
let mut foo = Foo{ref_data: Some(&string)};
drop(foo);
foo = Foo { ref_data: None };
string = "mutated string".to_string();
foo.ref_data = Some(&string);
like image 1
user4815162342 Avatar answered Oct 18 '22 05:10

user4815162342