Reference from one struct member to another

How can I make something like this compile?

struct MyStruct<'a> {
    v: String,
    r: &'a String
}
fn main() {
    let s = String::from("test");
    MyStruct{v:s, r:&s};
}

You can't in safe Rust, and getting around the restriction with unsafe code is tricky and discouraged. You'll need to restructure your code so that the owned String and the borrowing &String don't live in the same struct, or replace the reference with a usize index.

"Self-referential struct" is the usual term for this construction, and you can search this forum for that phrase to find more discussion of the issues they cause from past threads. For example, if you could safely construct a value of your type MyStruct<'a>, you could (safely) cause that value to be moved from one location in memory to another (by putting it in a Box, say), after which the r field would be a dangling pointer because it would still point into the previous location, and that's undefined behavior.

8 Likes

What are you trying to achieve? Storing a reference to a String next to that same String doesn't give you anything useful to work with. Do you want to save a sub-string, that will be shrunk, later? The correct type for that use-case would be &str.

However, as @cole-miller already pointed out, that doesn't work. &str boils down to the type { pointer: *const u8, length: usize }. If you store an offset instead of a pointer, you can reconstruct the pointer whenever you need to work with r.

MyStruct {
    v: String,
    r_offset: usize,
    r_length: usize,
}

Constructing MyStruct:

let v: String = "Hello, World!".to_owned();
let r_offset: isize = 0;
let r_length: usize = v.len();
let my_struct = MyStruct { v, r_offset, r_length };

Obtaining a &str/&mut str from MyStruct:

impl MyStruct {
    fn get(&self) -> &str {
        let data: *const u8 = self.v.as_ptr().offset(self.r_offset);
        let slice: &[u8] = unsafe { std::slice::from_raw_parts(data, self.r_length) };

        unsafe { std::str::from_utf8_unchecked(slice) }
    }

    fn get_mut(&mut self) -> &mut str {
        let data: *mut u8 = self.v.as_mut_ptr().offset(self.r_offset);
        let slice: &mut [u8] = unsafe { std::slice::from_raw_parts_mut(data, self.r_length) };

        unsafe { std::str::from_utf8_unchecked_mut(slice) }
    }
}

Now, what's missing is an example to update the sub-string and depending on your use-case, you may even want to update the String, which would get quite messy.

At this point, I'd probably just scrap the internally unsafe API. I'd store r_begin: usize and r_end: usize instead of r_offset and r_length respectively and use the indexing operation v[r_begin..r_end], which guarantees memory-safety by sacrificing a bit of runtime performance, as it includes boundary checks, which are unlikely to be optimized away.

Luckily, most of the time, you don't need to squeeze out every bit of performance, i.e. having to use unsafe is rarely necessary. Rust is plenty fast as it is.

2 Likes

The Ouroboros crate is one of the more popular ways of creating self-referential structs.

https://crates.io/crates/ouroboros

These examples show alternative ways to use it.

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.