Express Partial<T> in trait function signature

I am struggling to express something in rust.

I have the following trait

#[async_trait]
pub trait Store {
    type Error: Send + Sync;
    type Model;

    async fn create(&self, model: &Self::Model) -> Result<StoreCreateResult, Self::Error>;
    async fn update(&self, id: &str, update_payload: &Self::Model) -> Result<(), Self::Error>;
    async fn delete(&self, id: &str) -> Result<(), Self::Error>;
    async fn get(&self, id: &str) -> Result<Option<Self::Model>, Self::Error>;
}

I want to express to consumers of this Trait that the following signature

 async fn update(&self, id: &str, update_payload: &Self::Model) -> Result<(), Self::Error>;

Accepts a Partial<Self::Model> . Ie. a struct which is identical to the original struct, but with all fields marked as Option<T>

I have found the following topics:

And the optional_struct crate

optional_struct does exactly what I want. But is there a way to use this macro in the signature of a trait function?

Or what is the idiomatic way to express this in rust?

You want a trait with an associated type:

trait AsPartial {
    type Partial;
}

impl AsPartial for Foo {
    type Partial = OptionalFoo;
}

type Partial<T> = <T as AsPartial>::Partial;
1 Like

That’s awesome! But one thing…

In my situation I want to express the associated type Model as a partial.

I can’t implement AsPartial for the Model in this case

Any suggestions as to how I would express a Partial associated type, not with a concrete type like Foo in your example

I don't understand what you mean by that.

1 Like

In case you're coming from TypeScript (since what you want looks similar to the utility types in TS): there's nothing equivalent to its utility types in Rust as the type system is much less dynamic. All a Partial<T> type could do is store values of type T, or things you can derive from T (like <T as AsPartial>::Partial) or types that use T as type parameters, such as Option<T>.

You can require that Model implements it:

    type Model: AsPartial;
1 Like

Of course! Thanks for your help guys thats exactly what I want

@Heliozoa @H2CO3

So I just tried this out


pub trait AsPartial {
    type Partial;
}

#[async_trait]
pub trait Store: Send + Sync {
    type Error: Send + Sync;
    type Model: AsPartial;

    async fn create(&self, model: &Self::Model) -> Result<Self::Model, Self::Error>;
    async fn update(
        &self,
        id: &str,
        update_payload: &Self::Model::Partial,
    ) -> Result<(), Self::Error>;
    async fn delete(&self, id: &str) -> Result<(), Self::Error>;
    async fn get(&self, id: &str) -> Result<Option<Self::Model>, Self::Error>;
}

And I don't think it works quite how I expected.

I get an Ambiguous associated type error - which makes sense.

ambiguous associated type [E0223]
2. if there were a trait named `Example` with associated type `Partial` implemented for `<Self as Store>::Model`, you could use the fully-qualified path: `<<Self as Store>::Model as Example>::Partial` [E0223]

You can see in this line:

        update_payload: &Self::Model::Partial,

I am trying to say

"The signature of the update function should be a partial of the Model associated type"

Ideally I would even have this type generated automatically.

So an example impl would be like

#[optional_struct] // this would satisfy the AsPartial trait ideally
struct SomeStruct {
    blah: String
}
impl Store for Something {
    type Model = SomeStruct;
    
    fn update(&self, id: &str, update_paylpoad: OptionalSomeStruct)  {
        // Here the compiler has ensured the struct I define in update_paylaod
        // is a partial of the SomeStruct
    }
}

Maybe I am over generalising here, and perhaps it not important to enforce this Partial type for my consumers.

Edit: I think I have spent too long with Typescript, I'm starting to see that this behaviour adds little value to consumers in Rust anyway. As they will always have to specify exact types in the implementing signature.

I had imagined a scenario where consumers wouldn't even have to specify the type of update_payload but that is not how rust works

Changing

update_payload: &Self::Model::Partial,

to the fully qualified path as suggested should work (we don't need <Self as Store>::Model here because Model is defined in the trait)

update_payload: &<Self::Model as AsPartial>::Partial

It is a little unwieldy though. As you say the utility of the trait isn't too high if it wouldn't be used anywhere else in your code. It may be better to keep it simple and just add a type UpdateModel; to the trait and point out the optional_struct crate in the docs.

1 Like