Returning smart pointers from Index and IndexMut?

Hello,

I am trying to implement Index and IndexMut on a container, but return a smart pointer struct instead of a normal reference, but it seems these two things just aren't meant to fit together :cry:

First is the issue of the lifetime I need inside the pointer struct. Index wants the Output to be declared as part of the impl declaration, but then I don't have access to the lifetime of the container object; I need to wait until the function signature for that...

use core::ops::{Index};

struct MyContainer {
    records : Vec<String>
}

struct MyElementPointer<'a> {
    record : &'a String
}

impl <'a>Index<usize> for MyContainer {
    type Output = MyElementPointer<'a>;
    
    fn index(&'a self, idx: usize) -> &MyElementPointer<'a> {
        MyElementPointer{record : self.records.get(idx).unwrap()}
    }
}

fn main() {
    let my_container = MyContainer{records : vec!["one".to_string(), "two".to_string(), "three".to_string()]};
    
    println!("{}", my_container[1].record);
}

And if I could somehow work around the lifetime issue, (just for illustration, I made the "smart pointer" just clone the data so it can have a 'static lifetime....

use core::ops::{Index};

struct MyContainer {
    records : Vec<String>,
}

struct MyElementPointer {
    record : String
}

impl Index<usize> for MyContainer {
    type Output = MyElementPointer;
    
    fn index(&self, idx: usize) -> MyElementPointer {
        MyElementPointer{record : self.records.get(idx).unwrap().clone()}
    }
}

fn main() {
    let my_container = MyContainer{records : vec!["one".to_string(), "two".to_string(), "three".to_string()]};
    
    println!("{}", my_container[1].record);
}

Then I'd have my pick of errors between method index has an incompatible type for trait and cannot return reference to temporary value errors.

Moving on to IndexMut, it relies on the Output type being the same as Index, but in the case of smart pointers, I have a struct MyElementPointerMut that is a compliment to struct MyElementPointer, in the same way that std::cell::Ref and std::cell::RefMut are different types.

Is there another trait that allows access to the square-bracket slice syntax?

Has anyone succeeded at something like this?

Thank you.

You've got a fundamental issue here in that the Index trait is meant to return a reference to something that exists already.

This is intended to be a cheap operation (typically just a bit of pointer arithmetic) that returns &Self::Output and &mut Self::Output, not an arbitrary type which dereferences to Self::Output that you create inside the index() method.

The solution would be to create bespoke get() and get_mut() methods. It's not as nice as as bracket syntax, but also doesn't have all the constraints and connotations of the Index and IndexMut traits.

2 Likes

That's what I was afraid of. :frowning:

It seems like this trait might be more appropriate... when it exists. https://github.com/rust-lang/rfcs/issues/997. It looked like there was good progress about a year ago, but nothing since August 2020.

Thanks for the reply

If you make a struct like this:

#[repr(transparent)]
struct HacketyHack(String)

Then you can transmute &String to &HacketyHack. This won't let you add extra fields (because there's no room for them), but you could add custom methods to the returned value.

2 Likes

Interesting. I hadn't seen the #[repr(transparent)] directive before. In this case, extra fields is actually what I wanted, so it's not going to help, but an interesting thing to keep in my back pocket for some other cases.

Thanks!

The Reference has a chapter explaining the different #[repr] attributes. You may also find the original RFC interesting.

1 Like