Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Generic placeholders or default values in Rust

I'm trying to write a generic command line parser. I'm having trouble with "default" values for generic types. cmd.invoke() returns a Result<K, E>, so there's no problem there, but how do I represent a placeholder or default value for E when cmd_to_invoke is a None? In C#, I could use default(E). Is there a construct like this in Rust?

pub struct Cmd<K, E> {
    cmds: Vec<Cmd<K, E>>,
}

impl<K, E> Cmd<K, E> {
    pub fn invoke(&mut self, cmd_name: &str) -> Result<K, E> {
        let cmd_to_invoke = self.cmds.iter_mut().find(|cmd| cmd.name == cmd_name);
        if let Some(cmd) = cmd_to_invoke {
            cmd.invoke()
        } else {
            // Some default / placeholder value for E
            Err(/* ? */)
        }
    }
}
like image 265
John Stritenberger Avatar asked Sep 26 '17 02:09

John Stritenberger


People also ask

What are generics in Rust?

In Rust, generics refer to the parameterization of data types and traits. Generics allows to write more concise and clean code by reducing code duplication and providing type-safety. The concept of Generics can be applied to methods, functions, structures, enumerations, collections and traits.

What are generic associated types?

GATs (generic associated types) were originally proposed in RFC 1598. As said before, they allow you to define type, lifetime, or const generics on associated types. If you're familiar with languages that have "higher-kinded types", then you could call GATs type constructors on traits.


1 Answers

You probably are looking for Default.

For example:

pub struct Cmd<K, E> {}

impl<K, E: Default> Cmd<K, E> {
    pub fn invoke(&mut self, cmd_name: &str) -> Result<K, E> {
        let cmd_to_invoke = self.cmds.iter_mut().find(|cmd| cmd.name == cmd_name);
        if let Some(cmd) = cmd_to_invoke {
            cmd.invoke()
        } else {
            // Some default / placeholder value for E
            Err(Default::default())
        }
    }
}

It's worth noting though that most error types in std and popular crates do not implement Default.

The idiomatic way of dealing with multiple error types in Rust is to define your own error enum for your application, library or component, which implements From for each possible underlying error type. This generally makes code simpler and easier to read, and works very nicely with the ? operator. Here is an example.

If you are writing a library which really must work with any error type, then this won't work. In that case, you are probably left with constraining E to the std::error::Error trait and then making a custom error type which can be converted from that, possibly keeping reference to the underlying error as a trait object.

like image 104
Peter Hall Avatar answered Sep 21 '22 12:09

Peter Hall