Implement Index<Range<usize>> on a newtype wrapper

Hi all,

Playground

I'm a bit lost on implementing the Index trait on a newtype type, and I think I might have inadvertently stumbled into unsafe territory.

Briefly, I have a type called FooBase that I have defined using the newtype pattern. The new type is a wrapper around a Vec of values of type Bar. I'd like to slice instances of FooBase into something that is Foo-like and that wraps a slice of type &[Bar].

So far I've implemented the slice as a new type called FooSlice:

#[derive(Debug)]
struct FooBase(Vec<Bar>);

#[derive(Debug)]
struct FooSlice<'a>(&'a [Bar]);

Additionally, I've implemented a trait called Foo because I would like to operate on both FooBases and FooSlices irrespective of what they are:

trait Foo {
    fn bars(&self) -> &[Bar];
}

// ...

fn process_foos<T: Foo + Debug>(foos: T) {
    println!("{:?}", foos.bars())
}

My problems arise when I try to implement the Index trait so that I can write code such as

let foos = FooBase(vec![Bar, Bar, Bar, Bar, Bar]);
let foos_slice = &foos[1..3];

I've made an attempt at this, but I can't work out the lifetime annotations even with the compiler's help:

impl Index<Range<usize>> for FooBase {
    type Output = FooSlice<'a>;

    fn index(&self, range: Range<usize>) -> &Self::Output {
        unsafe { std::mem::transmute(&FooSlice(&self.0[range])) }
    }
}

leads to:

error[E0261]: use of undeclared lifetime name `'a`
  --> src/main.rs:23:28
   |
23 |     type Output = FooSlice<'a>;
   |                            ^^ undeclared lifetime
   |
help: consider introducing lifetime `'a` here
   |
23 |     type Output<'a> = FooSlice<'a>;
   |                ++++
help: consider introducing lifetime `'a` here
   |
22 | impl<'a> Index<Range<usize>> for FooBase {
   |     ++++

For more information about this error, try `rustc --explain E0261`.

Could anyone help me understand whether what I want is possible, preferably with safe Rust?

Additionally, do you think this design even makes sense? Specifically, I wonder whether the type safety I get by wrapping the Vec<Bar> in a new type is worth the trouble in slicing it. In my current project on which the above example is based, I have a few more methods defined on the new type wrapper FooBase, but it would probably be alright to move these to functions instead.

Thanks everyone for your help!

I don't think you can get Index to work with FooSlice. With your current design you create a FooSlice instance inside Index::index and return a reference to it. This is impossible to do in Rust. What I'd do instead (given your snippet—not sure if this translates to your actual use-case) is implement Foo for [Bar] (or &[Bar]) and omit FooSlice:

use std::fmt::Debug;
use std::ops::{Index, Range};

#[derive(Debug, Clone)]
struct Bar;

trait Foo {
    fn bars(&self) -> &[Bar];
}

#[derive(Debug)]
struct FooBase(Vec<Bar>);

impl Foo for FooBase {
    fn bars(&self) -> &[Bar] {
        &self.0
    }
}

impl Index<Range<usize>> for FooBase {
    type Output = [Bar];

    fn index(&self, range: Range<usize>) -> &Self::Output {
        &self.0[range]
    }
}

impl Foo for [Bar] {
    fn bars(&self) -> &[Bar] {
        self
    }
}

fn process_foos<T: Foo + Debug + ?Sized>(foos: &T) {
    println!("{:?}", foos.bars())
}

fn main() {
    let bars = vec![Bar, Bar, Bar, Bar, Bar];
    let foos = FooBase(bars.to_vec());
    let foos_slice = &foos[1..3];

    process_foos(&foos);
    process_foos(foos_slice);
}

Playground.

3 Likes

No, it is not possible. The signature of Index::index() means that you must return a literal reference, but there's no way to have a reference to anything created inside the function that would be valid outside the function, by definition.

You could modify the definition of FooSlice to be transparent and unsized, and achieve the indexing via unsafe code.

3 Likes

If you want to keep FooSlice, you can also write your own slicing method instead of using the Index operator, which removes the requirement to return &Something:

trait Foo {
    fn bars(&self) -> &[Bar];
    fn slice(&self, idx:Range<usize>)->FooSlice<'_> {
        FooSlice(&self.bars()[idx])
    }
}

// ...

fn process_foos<T: Foo + Debug>(foos: &T) {
    println!("{:?}", foos.bars())
}

fn main(){

    let bars = vec![Bar, Bar, Bar, Bar, Bar];
    let foos = FooBase(bars.to_vec());
    let foos_slice = foos.slice(1..3);

    process_foos(&foos);
    process_foos(&foos_slice);
}
1 Like

Thank you everyone for your feedback!

1 Like

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.