Traits that require properties

I know that Traits do not allow fields or properties, and I even understand why (after reading some of the discussions). And, I like the restriction, generally.

Traits can also have default implementations. This is great.

So, given these constraints, is there an idiomatic way to have that default implementation require that the implementing struct have a specific field? For example, with setters and/or getters?

I have about 50 "definition" structs, each with different combinations of the same 10 properties. They all have name and description for example. I have created traits for some of the combinations like HasMeta and HasDataTypes, which define the appropriate setters and getters, but each implementer still has to have those fields.

It would be nice to direct the implementor that they must have certain fields.

Am I missing something? Is there a more rust-ish way of doing this?

Thanks.

2 Likes

For reference, I found this unofficial RFC that hasn't moved in a while: https://github.com/nikomatsakis/fields-in-traits-rfc/blob/master/0000-fields-in-traits.md and this forum topic Fields in Traits - language design - Rust Internals

Why not factor out the commonalities?

struct Data<Meta: ?Sized> {
    name: String,
    description: String,
    meta: Meta
}

impl<Meta> Data<Meta> {
    fn new(name: String, description: String, meta: Meta) -> Self {
        Self {
            name,
            description,
            meta
        }
    }
}

impl<Meta: ?Sized> Data<Meta> {
    fn name(&self) -> &str {
        &self.name
    }
    
    fn description(&self) -> &str {
        &self.name
    }
    
    fn meta(&self) -> &Meta {
        &self.meta
    }
    
    // .. and the rest ..
}

struct Position {
    x: f32,
    y: f32,
    z: f32
}

impl Data<Position> {
    fn x(&self) -> f32 { self.meta.x }
    fn y(&self) -> f32 { self.meta.y }
    fn z(&self) -> f32 { self.meta.z }
}

// ... and others ...
1 Like

Yeah, I generally like that idea, but these definitions are mirrors of a json schema I don't have control over, and will be serialized and deserialized. I could still make that work, but I'd really rather just compose like I'm used to in other languages.

Since Rust doesn't really support OOP, you likely won't be able to do things in Rust the same way as other languages. This refactoring should make you code cleaner, and reduce code duplication. What are you using to serialize and deserialize?

2 Likes

I'm using Serde, and I'm sure I can make it dance to map the json schema to the struct schema. That's fine.

Not sure if I would rather do that, or just define traits with no defaults, let my IDE create the implementations, and spend some time typing self.x over and over, lol.

Well, if you use serde, could you use the derives?

Maybe? I'm not totally sure what you mean. I already derive Serialize and Deserialize. Are you suggesting creating my own macro? I didn't think a macro could insert fields into a struct, just add impl blocks and such before and/or after the struct.

Oh, no, I was asking if using the derives on the generic type that I suggested would be bad?

Well, I admit that I'm still not totally comfortable with derives, and I'm not sure I understand what you mean. Do you have a quick example? Or a doc link I could read?

Thanks for the help, btw. I'm loving rust, but its a mind-bender sometimes.

Ok, sorry I wasn't clear, I meant is this fine?

#[derive(Serialize, Deserialize)]
struct Data<Meta: ?Sized> {
    name: String,
    description: String,
    meta: Meta
}

Yeah, Rust is just operates so different from other languages that it takes some getting used to.

3 Likes

Oh, I follow. Yeah, that should work fine if I factor everything out. I just have to run that past my higher-ups. I was told to follow the JSON Schema if possible just to keep things consistent.

I could probably bend Serde to deserialize the json schema into a factored struct like you are describing. Just not sure if that passes muster.

1 Like

Ok, that's cool! I just got a bit confused when you said this

Yeah, I'm liking that idea more and more. It's more future-proof and flexible. Thanks. I'm going to scratch something together and see what works.

I guess if I needed to keep everything flat, my best option would be to create traits with no defaults, implement those traits, let my IDE create all the impl blocks, and then copy and paste.

Either way, it works. I guess I don't miss OOP so much after all :slight_smile:

1 Like

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