1

I'm new to Rust, probably missing something obvious. I have the following code with the main idea of being able to index any struct field like so struct_instance['field'].

use std::ops::Index;

enum Selection {
    Full,
    Partial,
}

struct Config {
    display: bool,
    timeout: u16,
    selection: Selection,
}

impl Index<&'_ str> for Config {
    type Output = bool;

    fn index(&self, index: &'_ str) -> &Self::Output {
        match index {
            "display" => &self.display,
            _ => panic!("Unknown field: {}", index),
        }
    }
}

fn main() {
    let config = Config {
        display: true,
        timeout: 500,
        selection: Selection::Partial,
    };

    let display = config["display"];

    println!("{display}");
}

The problem is: I can not find a way to index every type of struct fields, because associated type Output doesn't let me define more than one type. I would want to have match being able to process all Config fields somehow, is there a way to do so?

Herohtar
  • 5,347
  • 4
  • 31
  • 41
Slava.In
  • 597
  • 1
  • 9
  • 22
  • Why do you need to do this instead of using `config.display`? – apilat Sep 10 '22 at 21:40
  • @apilat very specific case where I can use one `&str` for both writing to file and accessing struct value. – Slava.In Sep 10 '22 at 21:43
  • 2
    The [`Index`](https://doc.rust-lang.org/std/ops/trait.Index.html) trait is mainly intended for arrays-like structures and doesn't really apply here. I would suggest not trying to use it and `match`ing the string directly. It's difficult to give a more detailed answer without seeing code, so please include a [reproducible example](https://stackoverflow.com/help/minimal-reproducible-example). – apilat Sep 10 '22 at 21:49
  • 2
    Rust is strong-typed language. Function returns a compile-time type. For returning "different" types you have to use dynamic dispatching (polymorphism). For instance, returning a `Box` where `Field` must be implemented for each field of your struct. Not really useful I think tho. (Or a `enum` with all possible types) – BiagioF Sep 10 '22 at 22:03

3 Answers3

1

As answered apilat , Index is for array like structures.

However if you want, you can achieve this with enums.

  1. Create enum with all available types of config fields (bool, u16, Selection, etc...)
  2. Change Config fields' types to this new enum
  3. Change the Output in the Index impl again to this new enum

Here is full code example

use std::ops::Index;

#[derive(Debug)]
enum ConfigField {
    Display(bool),
    Timeout(u16),
    Selection(Selection)
}

#[derive(Debug)]
enum Selection {
    Full,
    Partial,
}

struct Config {
    display: ConfigField,
    timeout: ConfigField,
    selection: ConfigField,
}

impl Index<&'_ str> for Config {
    type Output = ConfigField;

    fn index(&self, index: &'_ str) -> &Self::Output {
        match index {
            "display" => &self.display,
            "timeout" => &self.timeout,
            "selection" => &self.selection,
            _ => panic!("Unknown field: {}", index),
        }
    }
}

fn main() {
    let config = Config {
        display: ConfigField::Display(true),
        timeout: ConfigField::Timeout(500),
        selection: ConfigField::Selection(Selection::Partial),
    };

    let display = &config["display"];
    let timeout = &config["timeout"];
    let selection = &config["selection"];

    println!("{:?} {:?} {:?}", display, timeout, selection);
}
1

Building an an actual Index impl around this is hard. It can be done through a side effect to the index argument:

use miniconf::{JsonCoreSlash, Miniconf};
use serde::{Deserialize, Serialize};

#[derive(Deserialize, Serialize)]
enum Selection {
    Full,
    Partial,
}

#[derive(Miniconf)]
struct Config {
    display: bool,
    timeout: u16,
    selection: Selection,
}

impl core::ops::Index<(&str, &mut &mut [u8])> for Config {
    type Output = ();
    fn index(&self, (path, buf): (&str, &mut &mut [u8])) -> &Self::Output {
        let len = self.get_json(path, *buf).unwrap();
        *buf = &mut core::mem::take(buf)[..len];
        &()
    }
}

fn main() {
    let config = Config {
        display: true,
        timeout: 500,
        selection: Selection::Partial,
    };

    let mut buf = [0; 64];
    let mut slic = &mut buf[..];

    config[("/display", &mut slic)];

    println!("{}", core::str::from_utf8(slic).unwrap());
}
  • 1
    While this uses indexing notation, this doesn't fulfill people's expectations for indexing. So this is way worse than a method. – Chayim Friedman Aug 16 '23 at 12:06
-1

If you really need to return a reference/mutable ref to the item in your struct, you'll need trait objects, reflection, and something like bevy_reflect.

If you can live with serializing and deserializing your items, you can try partial serialization/deserialization, and e.g. miniconf. For your code it looks like this:

use miniconf::{JsonCoreSlash, Miniconf};
use serde::{Deserialize, Serialize};

#[derive(Deserialize, Serialize)]
enum Selection {
    Full,
    Partial,
}

#[derive(Miniconf)]
struct Config {
    display: bool,
    timeout: u16,
    selection: Selection,
}

fn main() {
    let config = Config {
        display: true,
        timeout: 500,
        selection: Selection::Partial,
    };

    let mut buf = [0; 64];
    let len = config.get_json("/display", &mut buf).unwrap();
    let display = core::str::from_utf8(&buf[..len]).unwrap();

    println!("{display}");
}

You can also build your Index trait implementation around those three lines.

From this you can continue and build deeper hierarchies. Miniconf was originally designed for your use case of configuration management, in embedded devices (it is no_std).

  • 1
    "You can also build your `Index` trait implementation around those three lines." How, exactly? I don't think this is possible. – Chayim Friedman Aug 09 '23 at 13:03