Zombie_Pigdragon
Zombie_Pigdragon

Reputation: 354

Ergonomically passing a slice of trait objects

I am converting a variety of types to String when they are passed to a function. I'm not concerned about performance as much as ergonomics, so I want the conversion to be implicit. The original, less generic implementation of the function simply used &[impl Into<String>], but I think that it should be possible to pass a variety of types at once without manually converting each to a string.

The key is that ideally, all of the following cases should be valid calls to my function:

// String literals
perform_tasks(&["Hello", "world"]);
// Owned strings
perform_tasks(&[String::from("foo"), String::from("bar")]);
// Non-string types
perform_tasks(&[1,2,3]);
// A mix of any of them
perform_tasks(&["All", 3, String::from("types!")]);

Some various signatures I've attempted to use:

fn perform_tasks(items: &[impl Into<String>])

The original version fails twice; it can't handle numeric types without manual conversion, and it requires all of the arguments to be the same type.

fn perform_tasks(items: &[impl ToString])

This is slightly closer, but it still requires all of the arguments to be of one type.

fn perform_tasks(items: &[&dyn ToString])

Doing it this way is almost enough, but it won't compile unless I manually add a borrow on each argument.

And that's where we are. I suspect that either Borrow or AsRef will be involved in a solution, but I haven't found a way to get them to handle this situation. For convenience, here is a playground link to the final signature in use (without the needed references for it to compile), alongside the various tests.

Upvotes: 2

Views: 815

Answers (2)

Joe_Jingyu
Joe_Jingyu

Reputation: 1279

The following way works for the first three cases if I understand your intention correctly.

pub fn perform_tasks<I, A>(values: I) -> Vec<String> 
where
    A: ToString,
    I: IntoIterator<Item = A>,
{
    values.into_iter().map(|s| s.to_string()).collect()
}

As the other comments pointed out, Rust does not support an array of mixed types. However, you can do one extra step to convert them into a &[&dyn fmt::Display] and then call the same function perform_tasks to get their strings.

let slice: &[&dyn std::fmt::Display] = &[&"All", &3, &String::from("types!")];
perform_tasks(slice);

Here is the playground.

Upvotes: 1

whilrun
whilrun

Reputation: 1724

If I understand your intention right, what you want is like this

fn main() {
    let a = 1;
    myfn(a);
}

fn myfn(i: &dyn SomeTrait) {
   //do something
}

So it's like implicitly borrow an object as function argument. However, Rust won't let you to implicitly borrow some objects since borrowing is quite an important safety measure in rust and & can help other programmers quickly identified which is a reference and which is not. Thus Rust is designed to enforce the & to avoid confusion.

Upvotes: -1

Related Questions