How to fix the compile error for the reference in the heap

I have a model and some string content as original, and want to parse the content for it, i don't want to copy the content, so i use Rc<&str>, but compiler complains. please help me to fix the error, thanks. the code is here:

use std::rc::Rc;
use std::collections::HashMap;

#[derive(Debug)]
struct A<'a> {
    content: String,
    the_map: HashMap<char, Rc<&'a str>>,
    other: i32,
}

impl<'a> A<'a> {
    fn new() -> A<'a> {
        let mut aaa = A {
            content: "hello, world".to_string(),
            the_map: HashMap::new(),
            other: 0,
        };
        parse(&mut aaa);
        
        aaa
    }
}

fn parse<'a>(aaa: &'a mut A<'a>) {
    let the_map = &mut aaa.the_map;
    let words: Vec<Rc<&str>> = aaa.content.split(",").map(|x| Rc::from(x)).collect();

    the_map.insert(words[0].chars().nth(0).unwrap(), words[0].clone());

    let other = &mut aaa.other;
    *other = 20;
}

fn main() {
    let a = A::new();
    println!("{:?}", a);
}

the compile error is:

   Compiling playground v0.0.1 (/playground)
error[E0505]: cannot move out of `aaa` because it is borrowed
  --> src/main.rs:20:9
   |
11 | impl<'a> A<'a> {
   |      -- lifetime `'a` defined here
12 |     fn new() -> A<'a> {
13 |         let mut aaa = A {
   |             ------- binding `aaa` declared here
...
18 |         parse(&mut aaa);
   |               -------- borrow of `aaa` occurs here
19 |         
20 |         aaa
   |         ^^^
   |         |
   |         move out of `aaa` occurs here
   |         returning this value requires that `aaa` is borrowed for `'a`

error[E0515]: cannot return value referencing local variable `aaa`
  --> src/main.rs:20:9
   |
18 |         parse(&mut aaa);
   |               -------- `aaa` is borrowed here
19 |         
20 |         aaa
   |         ^^^ returns a value referencing data owned by the current function

Some errors have detailed explanations: E0505, E0515.
For more information about an error, try `rustc --explain E0505`.

There are no benefits[1] to wrapping an &str in Rc. Rc is for multiple ownership of some data, but &'a str continues to be a borrow of some other string it doesn't own, whether or not it is wrapped in Rc.

You can create Rc<str> instead, which will make an owned String-like value that can share the same allocation for its contents with any clones of it. However, this does not let you share substrings of one buffer — to do that, you have to go beyond std, which does not contain any such type. I'm a fan of arcstr::Substr, but you can also write your own type — it's just a struct containing Rc<str> or Arc<str> and a Range<usize> specifying what substring it accesses, plus the needed Deref and Index implementations and so on.

Once you have done that, you will no longer need a lifetime parameter <'a> on A, which is good, because this code also cannot work:

Using the same lifetime inside and on a &mut, as you have done here, creates a single mutable borrow that lasts for the rest of the A's existence (because A is only valid as long as its lifetime parameter, and &'a mut T implies that 'a is not longer than T's validity, and putting those constraints together means that 'a must be exactly as long as the A's existence). It is nearly always wrong to write such a signature.

The reason it comes up here is because that kind of perpetual borrow is exactly what you would need to create a self-referential data structure (aaa.the_map is borrowing aaa.content), but then, as you discovered, you can't actually use that data structure, or even create more than one such borrow. So, you have to use other solutions such as Rc. But you have to use Rc instead of the self-reference, not in addition to the self-reference.


  1. except in very specific cases where you need a thin pointer ↩︎

5 Likes

The reason for use Rc<&str> is to avoid copy content in heap, so use slice of string, which is &str , Rc<str> copy content underlying.


  1. except in very specific cases where you need a thin pointer ↩︎

A &str is just a pointer to a sequence of characters; it doesn't own the characters themselves. An Rc<&str> does not copy the underlying content, as you haven't given it any content.

Yeah, That’s it. But how to fix the above problem?

Store ranges (start..end) instead of any direct reference in the HashMap.

Or store the content and the map in separate data structures (using just &str) to avoid being self-referencial.

Please take another look at the second paragraph of my previous post; I mentioned several options: Rc<str>, arcstr::Substr, or storing ranges yourself. quinedot's suggestion of storing ranges without any pointer also works, but may or may not be convenient for your application.

No, the Rc will just increment its counter when you clone it.

Clone is ok, but Rc str really copy the content for “the content field of struct A” for the example codes above. when it’s count equals 0, it then released.

That’s right, I want use standard library, and don’t need copy, maybe only store (start, end) tuple will fix this issue

1 Like

The encapsulation is Normal, the reference is readonly, but compiler limits the expression of codes.

Can we add a readonly property for the content field?

It's true that the language doesn't support self-referencial structs (i.e. using &/&mut references) like your OP in a useful way. (Hence all the advice being to use something non-self-referencial.)

Readonly fields won't help, it's a deeper property of the borrowing model. You can't move borrowed things (for one example).

Maybe can add a readonly property, and make it like pinned variable

That wouldn't help your OP, where you move it.

But there are other problems anyway. Perhaps foremost, liveness of values are not what Rust lifetimes are. There is no lifetime for "until this field/containing struct goes away". That's a dynamic property, but lifetimes (those '_ things) are complie time checked and don't exist at runtime.

Lifetime carrying references are the wrong tool for the job. If Rust gets language support for cases like your OP, it will be something new. (It won't happen anytime soon.)

There are some crates that support the use case (but generally aren't completely sound).

It is clear that mutating content would invalidate the_map. Thus, it seems natural that content would be a &str instead of String, which would remove all the obstacles. No need of Rc whatsoever.

Code
use std::collections::HashMap;

#[derive(Debug)]
struct A<'a> {
    content: &'a str,
    the_map: HashMap<char, &'a str>,
    other: i32,
}

impl<'a> A<'a> {
    fn new() -> A<'a> {
        let mut aaa = A {
            content: "hello, world",
            the_map: HashMap::new(),
            other: 0,
        };
        parse(&mut aaa);
        
        aaa
    }
}

fn parse<'a>(aaa: &mut A<'a>) {
    let the_map = &mut aaa.the_map;
    let words: Vec<&str> = aaa.content.split(",").collect();

    the_map.insert(words[0].chars().nth(0).unwrap(), words[0]);

    let other = &mut aaa.other;
    *other = 20;
}

fn main() {
    let a = A::new();
    println!("{:?}", a);
}

If you really want to mutate content, then you can either change the references inside the_map into ranges as suggested previously or manage it though some internal methods with careful usage of unsafe/crates.

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.