Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Rust: Shorten generic type bounds

Tags:

generics

rust

Is there a way to shorten generic type bounds in rust? This is the mess I have to put on a lot of structs impls etc.:

pub struct IncomingClientMessageWithAddress<State, Msg>
    where State: AppState + Clone + serde::Serialize + serde::de::DeserializeOwned + std::marker::Unpin + 'static,
          Msg: AppEvent + Clone + serde::Serialize + serde::de::DeserializeOwned + std::marker::Unpin + 'static {
...

I am basically looking to do something like this (I know that below doesn't work for traits):

type MyStateAlias = AppState + Clone + serde::Serialize + serde::de::DeserializeOwned + std::marker::Unpin + 'static;
type MyEventAlias = AppEvent + Clone + serde::Serialize + serde::de::DeserializeOwned + std::marker::Unpin + 'static;


pub struct IncomingClientMessageWithAddress<State, Msg>
    where State: MyStateAlias,
          Msg: MyEventAlias {
...
like image 368
Krampenschiesser Avatar asked Jan 25 '23 02:01

Krampenschiesser


2 Answers

I sometimes realized that all occurrences of a certain trait are associated with other traits. In your case, if AppState always occurs with Clone and Serialize, you could already require these on top of state:

trait AppState : Clone + Serialize {/*...*/}

If not, you could still define an auxiliary trait

trait AuxAppState: AppState + Clone + Serialize {/*...*/}

and require State : AuxAppState.

Then, to automatically derive AuxAppState you'd have to impl it for every type that also implements State, Clone and Serialize:

impl<T> AuxAppState for T where T: AppState + Clone + Serialize {}

Finally, defining and implementing the AuxAppState could possibly be done by a macro to save some keystrokes:

macro_rules! auxiliary_trait{
    ($traitname: ident, $($t:tt)*) => {
        trait $traitname : $($t)* {}
        impl<T> $traitname for T where T: $($t)* {}
    }
}

All of this can possibly be done one day with trait aliases.

Moreover, I started to require trait bounds only where they are actually needed. E.g. in many cases, the struct-definition itself does not rely on the trait bounds, only the impl, so I started omitting them on the struct, only keeping them in the impl.

like image 63
phimuemue Avatar answered Jan 29 '23 10:01

phimuemue


You could use new traits with blanket implementations for this:

use serde::Serialize;
use serde::de::DeserializeOwned;
use std::marker::Unpin;

pub trait AppState {}
pub trait AppEvent {}

pub trait StateTrait : AppState + Clone + Serialize + DeserializeOwned + Unpin + 'static {}
impl<T : AppState + Clone + Serialize + DeserializeOwned + Unpin + 'static> StateTrait for T {}
pub trait EventTrait : AppEvent + Clone + Serialize + DeserializeOwned + Unpin + 'static {}
impl<T : AppEvent + Clone + Serialize + DeserializeOwned + Unpin + 'static> EventTrait for T {}

pub struct IncomingClientMessageWithAddress<State, Msg>
    where State: StateTrait, Msg: EventTrait
{
    ... 
}

playground

like image 21
TheNappap Avatar answered Jan 29 '23 10:01

TheNappap