How to make default generic param work?

I am trying to make convenient API where hashing algorithm parameter would be optional and default is used if none is specified. But I can not find good description on how default generic params work.
Most convenient would be function generic defaults, but as I understand it was axed: https://github.com/rust-lang/rust/issues/36887

I am a little bit surprised that I can declare struct with default generic param, but I can not create it without explicit specification of the param.
Here is what I have, but I can not figure out, how to make default type info flow from struct into impl:

mod scratch {
    use failure::_core::marker::PhantomData;

    trait Hasher {
        fn hash(key: &[u8]) -> u32;
    }

    struct Murmur2 {}
    struct Crc32 {}
    impl Hasher for Murmur2 { fn hash(key: &[u8]) -> u32 {unimplemented!()} }
    impl Hasher for Crc32 { fn hash(key: &[u8]) -> u32 {unimplemented!()} }

    // I want murmur to be the default hasher
    struct Producer<H: Hasher=Murmur2> {
        phantom: std::marker::PhantomData<H>,
    }
    impl<H: Hasher> Producer<H> {
        fn send(&mut self, data: &[u8]) {
            let hash = H::hash(data);
        }
    }

    // Usage
    fn call_it() {
        let producer = Producer {phantom: PhantomData};
        //             ^^^^^^^^ cannot infer type for type parameter `H` declared on the struct `Producer`
    }
}

Not exactly what you asked for but you could implement a 'default' constructor:

// edit: applying @alice's note
impl Producer<Murmur2> {
    fn new() -> Self {
        Producer { phantom: PhantomData }
    }
}

fn call_it() {
    let p = Producer::new();
}
1 Like

The default only has any influence on what happens if you are mentioning the type (e.g. in a struct field), not what happens when you're constructing it. Take a look at how HashMap does it. Their new function is defined like this:

impl<K, V> HashMap<K, V, RandomState> {
    pub fn new() -> HashMap<K, V, RandomState> { ... }
}

If you want to use a specific hasher, there's a separate method in a different impl block:

impl<K, V, S> HashMap<K, V, S> {
    pub fn with_hasher(hash_builder: S) -> HashMap<K, V, S> { ... }
}
1 Like

Ah, I understand now that what escaped me is a "specialization" which impl Producer<Murmur2> {...} provides. Now I do not even need default generic at all

The default only has any influence on what happens if you are mentioning the type (e.g. in a struct field)

Thanks @alice, that was another missing bit of understanding.

I've ended up with this api, which is aesthetically pleasing :slight_smile:

use failure::_core::marker::PhantomData;

trait Hasher {
    fn hash(key: &[u8]) -> u32;
}

struct Murmur2 {}
struct Crc32 {}
impl Hasher for Murmur2 { fn hash(key: &[u8]) -> u32 {unimplemented!()} }
impl Hasher for Crc32 { fn hash(key: &[u8]) -> u32 {unimplemented!()} }

// I want murmur to be the default hasher
struct Producer<H: Hasher> {
    phantom: std::marker::PhantomData<H>,
}

impl Producer<Murmur2> {
    fn new() -> Self { Producer::with_hasher() }
}

impl<H: Hasher> Producer<H> {
    fn with_hasher() -> Self { Producer {phantom: PhantomData} }
    fn send(&mut self, data: &[u8]) {
        let hash = H::hash(data);
    }
}

// Usage
fn call_it() {
    let mut producer = Producer::new();
    let mut producer_crc: Producer<Crc32> = Producer::with_hasher();
    producer.send(&[1,2,3]);
    producer_crc.send(&[1,2,3]);
}
1 Like

This topic was automatically closed 90 days after the last reply. New replies are no longer allowed.