Refering to own members while constructing an object

Hi I have a struct that contains a String data and a view of that raw data. To minimize copying I opted for this design. Problem is, however, that I cannot seem to construct the view by referring to the previously assigned member.

pub struct MyType <'x> { 
    raw_data: String,
    pub view: &'x str
}

impl <'x> MyType <'x> {
    pub fn construct(arg: String) -> Self {
        MyType {
            raw_data: arg,
            view: &raw_data[1 .. ] // E: a field by this name exists in `Self`
        }
    }
}

fn main() {
    let my_obj = MyType::construct(String::from("ABCDEFGH"));
    println!("{}", my_obj.view);
}

Obviously, the BC would prevent me from refegring to &arg[1..] instead. What should I do?

Remember that

  1. The arg is a big (upto 10k chars) string, and evaluation of arg is very expensive (takes upto 1 second in my decent system). Which is why I'm trying to reduce latency as much as I can wherever possible.
  2. The view is just a small example, in the codebase, the view is actually structs containing more structs but they all internally have &strs that refer to the big raw_data String's different substrings.

Seperate raw and view into different types. You are trying to create a self referential type, and you can't do anything useful with those unless you use some tricky unsafe code.

Instead you can have 1 type to store the expensive calculations and many others that are simply views into the first.

2 Likes

When doing this

OP can still hold the values in the same struct right?
Something like this

pub struct Wrapper<'a>(&'a str);

impl<'a> std::ops::Deref for Wrapper<'a> {
    type Target = &'a str;
    fn deref(&self) -> &Self::Target {
        &self.0
    }
}

pub struct MyType <'x> { 
    raw_data: String,
    pub view: Wrapper<'x>
}

impl <'x> MyType <'x> {
    // Note that String is now &str, it needs a lifetime to compile
    pub fn construct(arg: &'x str) -> Self {
        let view = Wrapper(&arg);
        MyType {
            // my example may not be great because of this
            raw_data: arg.to_owned(),
            view,
        }
    }
}

playground

But now you no longer have a view into raw_data, and uou are copying stuff. This is counter productive, you may as well just have &str and not String in MyType.

What about something like is there any reason this wouldn't work?

// the different types could be returned from different methods?
pub fn view(&self) -> &str {
    &self.raw_data[1..]
}

Or are there costs to this I'm not seeing?

1 Like

That would also work, the only costs is the indexing on every use, but that should be fine in the grand scheme of things.

1 Like

One thing to note is that you can have and use a self-referential type under certain circumstances:

  • We construct and assign the self-reference after having run the constructor for the type (I mean the declarative struct syntax such as MyType { data: my_string, view: None }).
  • We cannot move this type afterwards, but can use it as normal in the same scope.

Why?
We can declare a type:

struct MyType<'a>(String, Option<&'a str>);

let mut x = MyType(String::new(), None); //'a is indeterminate currently
x.1 = Some(&x.0); //We've assigned x.1 a value with x's lifetime
//Therefore if we could name x's lifetime, its type would be
// MyType<'x>
//Hence it borrows from itself for the remainder of its lifetime.
//We can only run `&self` methods though, because we could do something 
//like so in an `&mut self` method:
x.1 = String::from("abc");
println!("{}", x.0); // Garbage

Although, if we did something like the following:

struct MyType<'a>(String, Option<&'a mut str>);

let mut x = MyType(String::new(), None);
x.1 = Some(&mut x.0);

We wouldn't be able to do anything with x because it now borrows from itself mutably, meaning we can't even run &self methods like before!

5 Likes