< 返回版块

leslieDD 发表于 2021-05-04 10:04

#![allow(dead_code)]
struct Sheep {}

trait Animal {
    fn new() -> Self ;
    fn noise(&self) -> &'static str;
}

impl Animal for Sheep {
    fn new() -> Self{
        Sheep{}
    }
    fn noise(&self) -> &'static str {
        "xxxxxxxxxxxxxx"
    }
}

fn f1() -> impl Animal {
    Sheep {}
}

fn f2<T: Animal>(t: T) -> T {
    t
}

fn f3<T: Animal>() -> T {
    T::new()
}

fn f4<T: Animal>() -> T {
    Sheep{}
}

f4函数会报:

mismatched types
expected type parameter `T`

这个要怎么解释,为什么f1,f2,f3可以,f4就不行了呢

补充:

可能原因是:返回的值是实现了某个trait的对象,所以生成这个对象也得是通过trait的方法生成。 希望有智慧的大神可以指点一下~~

转过弯了,谢谢大家~~

评论区

写评论
modraedlau 2021-05-04 13:00

要理解这个问题首先要明确的是rust的泛型是会单态化的,也就是fn f4<T: Animal>() -> T类似的签名最终会被所有实现Animal的进行展开:fn f4<Sheep>() -> Sheepfn f4<Cattle>() -> Cattle... 显然你在f4中只返回Sheep肯定不行。

那么为什么fn f1() -> impl Animal可以呢,关于这个问题我们就要充分理解impl trait的使用了,其实impl trait和泛型还是有很多区别的。首先我来看下edition-guide中的impl Trait for returning complex types with ease,然后总结出impl trait使用使用上的特别之处:

首先,impl trait可以用于参数位置和返回值位置。

其次,impl trait用于参数位置的时候和使用泛型在功能上是很接近的,比如:

// almost the same

trait Trait {}

fn foo<T: Trait>(arg: T) {
}

fn foo(arg: impl Trait) {
}

但是区别是这里要特别注意使用impl trait时无法使用turbo-fish如:foo::<usize>(1)

其次,在返回位置时两者有明显的区别。考虑如下代码:

fn foo<T: Trait>() -> T {

When you call it, you set the type, T. "you" being the caller here. This signature says "I accept any type that implements Trait." ("any type" == universal in the jargon)

新版本:

fn foo<T: Trait>() -> T {

is similar, but also different. You, the caller, provide the type you want, T, and then the function returns it...

但是在使用impl trait时:

With impl Trait, you're saying "hey, some type exists that implements this trait, but I'm not gonna tell you what it is."

总结一下也就是说第一点,使用泛型返回时,具体的类型可由调用者来决定,而impl trait返回时其实可由函数本身来决定;第二点,泛型实际上是any的语义,而impl trait实际上是some的语义。

附:有关impl trait的设计可以查阅rfcs中的内容expand impl trait。文中在背景中有对anysome的说明,我摘录了一点:

Universal quantification, i.e. "for any type T", i.e. "caller chooses". This is how generics work today. When you write fn foo(t: T), you're saying that the function will work for any choice of T, and leaving it to your caller to choose the T.

Existential quantification, i.e. "for some type T", i.e. "callee chooses". This is how impl Trait works today (which is in return position only). When you write fn foo() -> impl Iterator, you're saying that the function will produce some type T that implements Iterator, but the caller is not allowed to assume anything else about that type.

Aya0wind 2021-05-04 11:56

你别把dyn trait和impl trait搞混了,前者是引用一个类型编译时未知,但是实现了该trait的对象,而后者则是编译的时候类型固定且编译时必须已知的,仅仅是一个约束而已,两者完全不同。
f4你约束了参数和返回值都是T类型,而T的具体类型是在调用这个函数的时候从参数或者函数的泛型参数推导得出的,而你的返回值却在没使用的时候就固定为了Sheep类型,这会造成类型不匹配。
泛型其实就是一个代码生成器,你的f4在使用的时候会生成一个具体的函数,例如你这么调用f4

f4::<Sheep>();

就会生成一个类似这样的函数

fn f4_Sheep() -> Sheep {
    Sheep {}
}

咋一看没啥问题,但是如果你这么用

struct Cat;
impl Animal for Cat {
    fn new() -> Self {
        Cat {}
    }
    fn noise(&self) -> &'static str {
        "xxxxxxxxxxxxxx"
    }
}
f4::<Cat>();

自然就会生成一个类似这样的函数

fn f4_Cat() -> Cat {
    Sheep {}
}

这样很明显就是错的,返回值类型与返回的类型不匹配。

如果你要返回一个Sheep类型,且参数也想用trait约束,那就用dyn trait,这个是可以的,类似java的interface。比如这样

fn f4<T: Animal>() -> Box<dyn Animal> {
    Box::new(Sheep {})
}
作者 leslieDD 2021-05-04 10:58

这种写法,上面有写,不过想知道f4为毛不行,特别是和f1对比起来看

--
👇
ljjava: fn f4<T: Animal>() -> T { T::new() }

作者 leslieDD 2021-05-04 10:39

报错是f4这个函数有语法错误。

--
👇
gwy15: 你要返回的是个泛型参数 T: Animal,但是你直接返回了 Sheep,如果这样写

f4::<Goat>()

不就类型不匹配了吗

ljjava 2021-05-04 10:37

fn f4<T: Animal>() -> T { T::new() }

gwy15 2021-05-04 10:30

你要返回的是个泛型参数 T: Animal,但是你直接返回了 Sheep,如果这样写

f4::<Goat>()

不就类型不匹配了吗

1 共 6 条评论, 1 页