Access tuple struct with one element more ergonomically

I want to use a tuple struct with one field in a program to make the compiler help me not do nonsensical things with a value clearly meaning something very specific. My only problem is, now I have to specify my_struct.0 each time I want to use the member instead of using just a name. Is there a way to do something like this?

struct MyStruct(Vec<u8>);

impl MyStruct {
    fn new() -> Self {
        MyStruct(Vec::new())
    }
}

let mut my_struct = MyStruct::new();

// index directly, without needing to specify my_struct.0[0] every time
my_struct[0] = 20;

Thanks for the help I know I am going to get from this awesome community :wink:

You can implement the Deref and DerefMut traits for MyStruct.

1 Like

And you can also implement Index and IndexMut that forwards to the Vec. Here’s a macro to automate this:

2 Likes

To clarify (c.f., Is using struct to wrap basic types a good idea?):

  • either your tuple struct is just a newtype to let static type checking help you detect mixing values (very good practice!), which seems to be your case:

    to make the compiler help me not do nonsensical things with a value clearly meaning something very specific

    In that case, you don’t care that much about abstraction besides type-level “tagging”, and you would like for it to “Just Work like the wrappee”. Then you follow @sfackler’s suggestion and implement Deref to get access to the wrappee (Vec<u8>) “read” methods (including (right-value) Indexing):

    struct MyStruct /* = */ (Vec<u8>);
    
    impl core::ops::Deref for MyStruct {
        type Target = Vec<u8>;
    
        fn deref (self: &'_ Self) -> &'_ Self::Target
        {
            &self.0
        }
    }
    

    as well as DerefMut to also get access to the wrappee “write” methods (such as (left-value) Indexing):

    impl core::ops::DerefMut for MyStruct {
        fn deref_mut (self: &'_ mut Self) -> &'_ mut Self::Target
        {
            &mut self.0
        }
    }
    
  • or your newtype is actually an important abstraction boundary around the wrappee, which just happens to be an implementation detail offering too rich of an API. Then you do not implement Deref on your newtype (since that would break the abstraction layer), but, as @kornel suggested, implement instead only the features / parts of the API that you want; in your case Index and IndexMut:

    pub
    struct MyStruct (
        Vec<u8>,
    );
    
    impl core::ops::Index<usize> for MyStruct {
        type Output = u8;
    
        fn index (
            self: &'_ Self,
            index: usize,
        ) -> &'_ Self::Output
        {
            &self.0[index]
        }
    }
    impl core::ops::IndexMut<usize> for MyStruct {
        fn index_mut (
            self: &'_ mut Self,
            index: usize,
        ) -> &'_ mut Self::Output
        {
            &mut self.0[index]
        }
    }
    
5 Likes

I don’t know which one I should use, but I think I should only derive Index and MutIndex. I want to try to implement a working library which can take arbitrarily large (contingent on the amount of free memory, of course) integers as input, add, negate, subtract and compare them (and if I feel adventurous, to multiply and divide them). I know there is a crate for this, but I want to do this as an exercise. I have this structure in mind:

I have two tuple structs, both with only a Vec<u8> member, one for signed and one for unsigned. I then have a trait which implements the Add, Neg, Sub and Not (and AddAssign and SubAssign). Then each of the two structs implements the my trait (because all the above should do the same in terms of bit manipulation, just the interpretation of the results differ between signed and unsigned). The specialized things (I/O and comparison) should be implemented separately for each type.

This topic was automatically closed 90 days after the last reply. New replies are no longer allowed.