I want to write T::associated_function(..) but sometimes T is too verbose to write, like a long tuple. Is there any way to avoid writing the full T?

pub trait Param {
    fn encode_type(out: &mut Vec<u8>);
}
pub trait Params {
    fn encode_type(out: &mut Vec<u8>);
}

// Some macros to extend the implementation for long tuples
impl_params_for_tuple!(T0: 0);
impl_params_for_tuple!(T0: 0, T1: 1);
impl_params_for_tuple!(T0: 0, T1: 1, T2: 2);
impl_params_for_tuple!(T0: 0, T1: 1, T2: 2, T3: 3);
..

fn test_params_mixed_types() {
    let mut buffer = Vec::new();
    let params = (
       1i8, 2i16, 3i32, 4i64, 5u8, 6u16, 7u32, 8u64, 1.5f32, 2.5f64, "hello",
    );


    // want to write something like `type_of(params)::encode_types`
    <(i8, i16, i32, i64, u8, u16, u32, u64, f32, f64, &str)>::encode_types(&mut buffer);

    assert_eq!(types.len(), 22);
}

9 Replies 9

I know the interface doesn't make it clear but "Best practices" is for questions that seek opinionated advice, not for factual questions like yours. You should probably delete this post (if you can't flag it for deletion by a mod) and post it with the correct question type (which is unfortunately incorretcly labeled as "Troubleshooting / Debugging" but despite this does include factual "How to" questions like this) as changing the type is currently not supported.

This question is exactly seeking opinionated advice, there can't be factual or 'correct' methods when you simplify the code.

Is there any way to avoid writing the full T?

Is a "How to" question with factual answers.

The general "How can I improve this" would be opinion based, but that's not the question you ask here.

Is this maybe a XY problem? In this case I would instead try using an enum Param with all the possible parameter types as enum options. Then use a Vec<Param> instead of tuples. Also can't you use a type alias?

While Rust doesn't have a general type_of(), trait methods can use Self to refer to the type that implements the trait. You can define an extension trait with a method that has a receiver (&self or equivalent), and which just calls Self::encode_types() in its implementation. This does not require modifying the original trait. For example:

pub trait Params {
    fn encode_types(out: &mut Vec<u8>);
}

impl Params for (i8, i16) {
    fn encode_types(_out: &mut Vec<u8>) {}
}
// ... any number of Params impls possible ...

pub trait ParamsExt: Params + Sized {
    /// foo.encode_types_of_val() roughly equivalent to type_of(foo)::encode_types()
    fn encode_types_of_val(&self, out: &mut Vec<u8>) {
        Self::encode_types(out);
    }
}
impl<T: Params> ParamsExt for T {}

#[test]
fn test_params_mixed_types() {
    let mut buffer = Vec::new();
    let params = (
        1i8, 2i16, // ...
    );

    params.encode_types_of_val(&mut buffer);
}

Playground

P.S. I agree that this should be a regular question.

I still don't get this category thing but I flagged the deletion of this post as you requested.

Vec<Param> - tuple is used for ergonomic API. Also enum Param cannot be extended by external lib which I need

ParamsExt - while it works - do people actually use this pattern and double the signature?

ParamsExt - while it works - do people actually use this pattern and double the signature?

Extension traits are very widely used in Rust, especially in async. They provide additional functionality (extension) that builds upon some more basic functionality. And that is how it's used here - ParamsExt::encode_types_of_val() is a useful extension implemented on top of Params::encode_types(), with (if I do say so myself) a reasonably elegant implementation.

It's not "doubling the signature" because the signature is different - encode_types_of_val() is to encode_types() as size_of_val is to size_of.

You don't even need an extension trait if you don't want one. You can just use a function:

fn encode_type<T: Params>(_: &T, out: &mut Vec<u8>) {
    T::encode_type(out)
}

This allows you to call encode_type(&params, &mut buffer).

@cdhowie Thanks for chiming in, this is more elegant. I used an extension trait because I started out by considering Self as the way to name the type. But a function generic over a T allows naming the type just as well, and with less code. The magic is in the type inference on the value, and that works equally well with method and function calls.

Playground

Your Reply

By clicking “Post Your Reply”, you agree to our terms of service and acknowledge you have read our privacy policy.

Start asking to get answers

Find the answer to your question by asking.

Ask question

Explore related questions

See similar questions with these tags.