user
user

Reputation: 195

Generic struct's `new()` function does not accept non generic assignments to generic structs field

I ran into a problem the other day when I started learning how generics worked on rust. The issue I encountered is in the following snippet:

struct Example<T> {
    a: T
}

impl<T> Example<T> {
    fn new() -> Self {
        Self {
            a: 5
        }
    }
}

Trying to compile this gives the following output:

error[E0308]: mismatched types
  --> src/lib.rs:11:16
   |
8  | impl<T> Example<T> {
   |      - this type parameter
...
11 |             a: 5
   |                ^ expected type parameter `T`, found integer
   |
   = note: expected type parameter `T`
                        found type `{integer}`

For more information about this error, try `rustc --explain E0308`.
warning: `url_shortener` (lib) generated 2 warnings
error: could not compile `url_shortener` due to previous error; 2 warnings emitted

An easy fix would be specifying the type that we want, like so:

impl Example<i32> {
    fn new() -> Self {
        Self {
            a: 5
        }
    }
}

But I don't really understand why keeping the type parameter general does not work. Also I have an example where changing the type parameter would not be so easy. Say we want to do something like the following:

use itertools::Itertools;


struct Example<T: Iterator<Item=String>> {
    a: T
}


fn get_permutations(n: i32) -> impl Iterator<Item=String> {
    (0..n)
    .map(|_| (97u8..=122u8).map(|x| x as char))
    .multi_cartesian_product().map(|v| v.iter().join(""))
}

impl<T: Iterator<Item=String>> Example<T> {
    fn new() -> Self {
        let test = get_permutations(4)
            .chain(
                get_permutations(3)
            )
            .chain(
                get_permutations(2)
            )
            .chain(
                get_permutations(1)
            );

        Self {
            a: test
        }
    }
}

The type of test is Chain<Chain<Chain<impl Iterator<Item = String>, impl Iterator<Item = String>>, impl Iterator<Item = String>>, impl Iterator<Item = String>>, and we can't simply change the generic type parameter T to that, since the impl Trait syntax is not allowed for type parameters.

We could try changing the return type of the get_permutations function so that it does not return impl Trait, but the actual type returned by get_permutations includes closures and I don't know how to write it out (or if it even is possible considering each closure is its own type(?)). Besides we might want to chain together more functions in it at a later date, and it would be bad to change its type every time we wanted to do that.

So it seems it would be better to have get_permutations return type to be some generic Iterator, the only solution that I can think of is using Boxes like so:

use itertools::Itertools;


struct Example {
    a: Box<dyn Iterator<Item = String>>
}


fn get_permutations(n: i32) -> Box<dyn Iterator<Item = String>> {
    (0..n)
    .map(|_| (97u8..=122u8).map(|x| x as char))
    .multi_cartesian_product().map(|v| v.iter().join(""))
}

impl Example {
    fn new() -> Self {
        let test = Box::new(get_permutations(4)
            .chain(
                get_permutations(3)
            )
            .chain(
                get_permutations(2)
            )
            .chain(
                get_permutations(1)
            ));

        Self {
            a: test
        }
    }
}

But I find this kind of ugly. I can also create the object without using new():

use itertools::Itertools;


struct Example<T: Iterator<Item=String>> {
    a: T
}


fn get_permutations(n: i32) -> impl Iterator<Item=String> {
    (0..n)
    .map(|_| (97u8..=122u8).map(|x| x as char))
    .multi_cartesian_product().map(|v| v.iter().join(""))
}

fn main(){
    let test = Example{a: get_permutations(4)
            .chain(
                get_permutations(3)
            )
            .chain(
                get_permutations(2)
            )
            .chain(
                get_permutations(1)
            )};
    
    for perm in test.a {
        println!("{perm}")
    }
}

But this wouldn't make much sense if it is the case that every Example object should contain this exact iterator. So, is there a better way of doing this without using Boxes?

Upvotes: -1

Views: 587

Answers (1)

kmdreko
kmdreko

Reputation: 59817

Your simple example doesn't work since generics are specified by the user, i.e. Example::<String>::new() wouldn't make sense with your implementation, since it would try assigning 5 to a String.

For your actual use-case, you want to be able to store a type that you don't want to name, and want to avoid trait objects. As of right now, this isn't possible AFAIK. There is an unstable feature, #![feature(type_alias_impl_trait)], which would be ideal and would look like this:

#![feature(type_alias_impl_trait)] // <------------

use itertools::Itertools;

type Permutations = impl Iterator<Item = String>; // <------------

struct Example {
    a: Permutations,
}

fn get_permutations(n: i32) -> impl Iterator<Item = String> {
    (0..n)
        .map(|_| (97u8..=122u8).map(|x| x as char))
        .multi_cartesian_product()
        .map(|v| v.iter().join(""))
}

impl Example {
    fn new() -> Self {
        let test = get_permutations(4)
            .chain(get_permutations(3))
            .chain(get_permutations(2))
            .chain(get_permutations(1));

        Self { a: test }
    }
}

Your stable options are to either use a trait object, Box<dyn Iterator<Item = String>>, as you've already found or you can keep a generic argument and have a function return a concrete but opaque type via Example<impl Iterator<Item = String>>:

use itertools::Itertools;

struct Example<T: Iterator<Item = String>> {
    a: T,
}

fn get_permutations(n: i32) -> impl Iterator<Item = String> {
    (0..n)
        .map(|_| (97u8..=122u8).map(|x| x as char))
        .multi_cartesian_product()
        .map(|v| v.iter().join(""))
}

impl<T: Iterator<Item = String>> Example<T> {
    fn new() -> Example<impl Iterator<Item = String>> { // <------------
        let test = get_permutations(4)
            .chain(get_permutations(3))
            .chain(get_permutations(2))
            .chain(get_permutations(1));

        Example { a: test }
    }
}

Both have their drawbacks since the former would require an allocation and dynamic dispatch, but the latter method is still an unnameable type (you can't store Example<impl ...> in a struct) and can still be overridden by users.

Upvotes: 1

Related Questions