7

Is there a way to perform an index access to an instance of a struct like this:

struct MyStruct {
    // ...
}

impl MyStruct {
    // ...    
}

fn main() {
    let s = MyStruct::new();
    s["something"] = 533; // This is what I need
}
Shepmaster
  • 388,571
  • 95
  • 1,107
  • 1,366
Incerteza
  • 32,326
  • 47
  • 154
  • 261
  • Answerers might be able to give more concrete examples if you explain what you want `s["something"] = 533` to *do*. Otherwise, we will just be able to show you the syntax and traits you will need to implement. – Shepmaster Jan 25 '15 at 06:08
  • @Shepmaster, you don't understand what `s["something"] = 533` does? – Incerteza Jan 25 '15 at 08:24
  • There's not enough context presented to truly sure what you want. It has the possibility of doing *anything Rust can do* (writing a file, sending data over the network, `panic`ing, so on) as long as in the end it returns a mutable reference to an integral variable (and it's not clear if you'd want a 16-, 32- or 64-bit value, but that's just being pedantic). – Shepmaster Jan 25 '15 at 17:12

3 Answers3

13

You can use the Index and IndexMut traits.

use std::ops::{Index, IndexMut};

struct Foo {
    x: i32,
    y: i32,
}

impl Index<&'_ str> for Foo {
    type Output = i32;
    fn index(&self, s: &str) -> &i32 {
        match s {
            "x" => &self.x,
            "y" => &self.y,
            _ => panic!("unknown field: {}", s),
        }
    }
}

impl IndexMut<&'_ str> for Foo {
    fn index_mut(&mut self, s: &str) -> &mut i32 {
        match s {
            "x" => &mut self.x,
            "y" => &mut self.y,
            _ => panic!("unknown field: {}", s),
        }
    }
}

fn main() {
    let mut foo = Foo { x: 0, y: 0 };

    foo["y"] += 2;
    println!("x: {}", foo["x"]);
    println!("y: {}", foo["y"]);
}

It prints:

x: 0
y: 2
Shepmaster
  • 388,571
  • 95
  • 1,107
  • 1,366
huon
  • 94,605
  • 21
  • 231
  • 225
  • do I have to use them both? – Incerteza Jan 24 '15 at 15:14
  • 3
    If you want both operations, yes. – huon Jan 24 '15 at 16:19
  • So how is `foo['y']` it beneficial from `f.y`? We have `f.y` anyway, right? But what I want is to have `foo['y']` **only** and not to able to do `f.y` at all. – Incerteza Jan 25 '15 at 05:04
  • 3
    It was just an example; there's no reason that the value retrieved with the index has to be a field of a struct. E.g. `HashMap` supports indexing with whatever it's key type is, including `&str`, say `map: HashMap<&str, u32>`, `let value: u32 = map["foo"];`. – huon Jan 25 '15 at 05:42
  • You probably don't understand my question. What does it have to do with HashMap as I want to use my own struct, not HashMap or BTreeMap **instead**? – Incerteza Jan 25 '15 at 05:58
  • 3
    @dbaupp is simply using `HashMap` as an example of another way that the `Index(Mut)` trait is implemented. Your only requirement is to return a (mutable) reference to the type `Index::Output`. How you implement that logic is *completely up to you*. Your original example is a bit sparse, so we are just guessing at possible things that you might find useful. – Shepmaster Jan 25 '15 at 06:03
  • 3
    @AlexanderSupertramp, I understand the question you asked perfectly (note, the one you actually asked isn't necessarily the one you *wanted* to ask :) ), and was giving a working example of using the traits. You just need to change the contents of the struct and the contents of the implementations to suit your use case. `HashMap` is just a custom struct defined in `std` that implements those traits, it hasn't got anything special and so is just another example of using those traits to get the `[]` notation. – huon Jan 25 '15 at 08:50
6

You want to use the Index trait (and its pair IndexMut):

use std::ops::Index;

#[derive(Copy, Clone)]
struct Foo;
struct Bar;

impl Index<Bar> for Foo {
    type Output = Foo;

    fn index<'a>(&'a self, _index: Bar) -> &'a Foo {
        println!("Indexing!");
        self
    }
}

fn main() {
    Foo[Bar];
}
Shepmaster
  • 388,571
  • 95
  • 1,107
  • 1,366
  • 1
    That's copy-paste from the documentation, how is it related to my question? I mean, how can I adopt it for my case where the index type is a string? – Incerteza Jan 24 '15 at 15:12
  • 11
    It is indeed copy-paste; why would I rewrite a perfectly serviceable example when the Rust documentation already provides it? I copied it here in case the link breaks over time. If you aren't able to translate from the examples in the documentation to your specific case, then you should have linked to the docs in your original question and asked a specific question about the part you are having trouble with. – Shepmaster Jan 24 '15 at 15:17
  • What are you trying to convince me in? – Incerteza Jan 25 '15 at 04:54
0

You may have a problem if the fields in MyStruct are not homogeneous in their type (e.g. not all i32). Then Index/IndexMut will be a bit trickier to use. You'd be in core::any::Any land with reflection and trait objects and would use something like bevy_reflect.

Alternatively, instead of Index/IndexMut where you're looking to get a ref or ref mut to a node, you could try serde::Serialize/serde::Deserialize as a means of accessing tme. That solves your type issue. Using your code from the question, below is the complete implementation using miniconf.

The trait supports accessing nodes by usize numeric field index or by &str field name. It works fine for deeper hierarchies, arrays and nested structs (the example below already shows some of that) and supports any serde backend (showing JSON for convenience here).

use miniconf::{JsonCoreSlash, Miniconf};

#[derive(Miniconf)]
struct MyStruct {
    something: i32,

    #[miniconf(defer(2))]
    other: [Option<u8>; 10],
}

fn main() {
    let mut s = MyStruct {
        something: 99,
        other: [Some(3); 10],
    };
    
    s.set_json("/something", b"533").unwrap();
    assert_eq!(s.something, 533);

    s.set_json("/other/4", b"21").unwrap();
    assert_eq!(s.other[4], Some(21));
}

Similar questions have been asked under: