14

I have a simple struct that I would like to implement Index for, but as a newcomer to Rust I'm having a number of troubles with the borrow checker. My struct is pretty simple, I'd like to have it store a start and step value, then when indexed by a usize it should return start + idx * step:

pub struct MyStruct {
    pub start: f64,
    pub step: f64,
}

My intuition is that I'd simply be able to take the signature of Index and plug in my types:

impl Index<usize> for MyStruct {
    type Output = f64;

    fn index(&self, idx: usize) -> &f64 {
        self.start + (idx as f64) * self.step
    }
}

This gives the error mismatched types saying expected type &f64, found type f64. As someone who has yet to fully understand how Rust's type system works, I tried simply slapping & on the expression:

fn index(&self, idx: usize) -> &f64 {
    &(self.start + (idx as f64) * self.step)
}

This now tells me that the borrowed value does not live long enough, so maybe it needs a lifetime variable?

fn index<'a>(&self, idx: usize) -> &'a f64 {
    &(self.start + (idx as f64) * self.step)
}

The error is the same, but the note now gives lifetime 'a instead of lifetime #1, so I guess that's not necessary, but at this point I feel like I'm stuck. I'm confused that such a simple exercise for most languages has become so difficult to implement in Rust, since all I want to do is return a computation from a function that happens to be behind a reference. How should I go about implementing Index for a simple structure where the value is calculated on demand?

Shepmaster
  • 388,571
  • 95
  • 1,107
  • 1,366
bheklilr
  • 53,530
  • 6
  • 107
  • 163

2 Answers2

3

The Index trait is meant to return a borrowed pointer to a member of self (e.g. an item in a Vec). The signature of the index method from the Index trait makes it impractical to implement it to have the behavior you described, as you'd have to store every value returned by index in self and ensure that the pointers remain valid until the MyStruct is dropped.

Francis Gagné
  • 60,274
  • 7
  • 180
  • 155
  • So would it simply be better to implement a method on MyStruct called `index` or `at` that took a `usize` and returned a `f64`? I'd also like to implement the ability to index with the equivalent to a Python slice (`Range` I think in rust), is this possible with the `...` syntax as a function argument? – bheklilr Aug 24 '16 at 03:05
  • You can write a standalone `index` method, but you won't be able to use it with the indexing syntax (`x[i]`). Range syntax produces values with types provided by the standard library (e.g. `x..y` produces a [`Range`](https://doc.rust-lang.org/stable/std/ops/struct.Range.html) value). – Francis Gagné Aug 24 '16 at 03:47
  • So basically I'm just out of luck here? Oh well, it isn't a huge deal, just a different paradigm to learn for a new language. – bheklilr Aug 24 '16 at 13:16
-1

This use case does not match the intuition for Index. When I see myStruct[3], my intuition is that, just as for arrays, I'm getting a pointer to some already-initialized data. The interface for Index corroborates this intuition.

I can see two things that you might potentially be trying to achieve:

  1. Getting nice indexing syntax for your datastructure.

In this case I would recommend against the premise of implementing Index and just provide a method that returns a f64 instead of an &f64 like so.

impl MyStruct {
    pub fn index(&self, idx: usize) -> f64 {
        self.start + (idx as f64) * self.step
    }
}

You don't get the operators, which is good because somebody reading [] would be mislead into thinking they were getting a pointer. But you do get the functionality you want. Depending on your use cases you may want to rename this method.

  1. Passing MyStruct to a parameter with Index bounds.

This is trickier with good reason. Index expects the data to be there before it asks for it. You can't generate and return it because index returns f64, and you can't generate it in the datastructure and return a pointer because it doesn't take a &mut self. You'd have to populate these values before the call to index. Some redesign would be in order, and the direction of that redesign would depend on the larger context of your problem.

Matthew Piziak
  • 3,430
  • 4
  • 35
  • 49
  • 1
    I think I see now that my intuition is different than what rust's conventions are. This information is somewhat difficult to find, though, since rust is still a very young language. My approach is basically your first suggestion, although I went with a trait that I named `At` with a single method `at` that is modeled off of the design of `Index`. I've already figured out how to make it automatically `At`-able with a `Range` if the `impl` for `At` exists, which is quite handy for Rust to be able to do. – bheklilr Aug 24 '16 at 15:57