Is it possible to use `impl Trait` as a function's return type in a trait definition?

前端 未结 4 1065
有刺的猬
有刺的猬 2020-11-28 15:45

Is it at all possible to define functions inside of traits as having impl Trait return types? I want to create a trait that can be implemented by multiple stru

相关标签:
4条回答
  • 2020-11-28 16:14

    As trentcl mentions, you cannot currently place impl Trait in the return position of a trait method.

    From RFC 1522:

    impl Trait may only be written within the return type of a freestanding or inherent-impl function, not in trait definitions or any non-return type position. They may also not appear in the return type of closure traits or function pointers, unless these are themselves part of a legal return type.

    • Eventually, we will want to allow the feature to be used within traits [...]

    For now, you must use a boxed trait object:

    trait A {
        fn new() -> Box<dyn A>;
    }
    

    See also:

    • Is it possible to have a constructor function in a trait?
    • Why can a trait not construct itself?
    • How do I return an instance of a trait from a method?

    Nightly only

    If you wish to use unstable nightly features, you can use existential types (RFC 2071):

    // 1.40.0-nightly (2019-11-05 1423bec54cf2db283b61)
    #![feature(type_alias_impl_trait)]
    
    trait FromTheFuture {
        type Iter: Iterator<Item = u8>;
    
        fn example(&self) -> Self::Iter;
    }
    
    impl FromTheFuture for u8 {
        type Iter = impl Iterator<Item = u8>;
    
        fn example(&self) -> Self::Iter {
            std::iter::repeat(*self).take(*self as usize)
        }
    }
    
    fn main() {
        for v in 7.example() {
            println!("{}", v);
        }
    }
    
    0 讨论(0)
  • 2020-11-28 16:15

    Fairly new to Rust, so may need checking.

    You could parametrise over the return type. This has limits, but they're less restrictive than simply returning Self.

    trait A<T> where T: A<T> {
        fn new() -> T;
    }
    
    // return a Self type
    struct St1;
    impl A<St1> for St1 {
        fn new() -> St1 { St1 }
    }
    
    // return a different type
    struct St2;
    impl A<St1> for St2 {
        fn new() -> St1 { St1 }
    }
    
    // won't compile as u32 doesn't implement A<u32>
    struct St3;
    impl A<u32> for St3 {
        fn new() -> u32 { 0 }
    }
    

    The limit in this case is that you can only return a type T that implements A<T>. Here, St1 implements A<St1>, so it's OK for St2 to impl A<St2>. However, it wouldn't work with, for example,

    impl A<St1> for St2 ...
    impl A<St2> for St1 ...
    

    For that you'd need to restrict the types further, with e.g.

    trait A<T, U> where U: A<T, U>, T: A<U, T> {
        fn new() -> T;
    }
    

    but I'm struggling to get my head round this last one.

    0 讨论(0)
  • 2020-11-28 16:19

    You can get something similar even in the case where it's not returning Self by using an associated type and explicitly naming the return type:

    trait B {}
    struct C;
    
    impl B for C {}
    
    trait A {
        type FReturn: B;
        fn f() -> Self::FReturn;
    }
    
    struct Person;
    
    impl A for Person {
        type FReturn = C;
        fn f() -> C {
            C
        }
    }
    
    0 讨论(0)
  • 2020-11-28 16:28

    If you only need to return the specific type for which the trait is currently being implemented, you may be looking for Self.

    trait A {
        fn new() -> Self;
    }
    

    For example, this will compile:

    trait A {
        fn new() -> Self;
    }
    
    struct Person;
    
    impl A for Person {
        fn new() -> Person {
            Person
        }
    }
    

    Or, a fuller example, demonstrating using the trait:

    trait A {
        fn new<S: Into<String>>(name: S) -> Self;
        fn get_name(&self) -> String;
    }
    
    struct Person {
        name: String
    }
    
    impl A for Person {
        fn new<S: Into<String>>(name: S) -> Person {
            Person { name: name.into() }
        }
    
        fn get_name(&self) -> String {
            self.name.clone()
        }
    }
    
    struct Pet {
        name: String
    }
    
    impl A for Pet {
        fn new<S: Into<String>>(name: S) -> Pet {
            Pet { name: name.into() }
        }
    
        fn get_name(&self) -> String {
            self.name.clone()
        }
    }
    
    fn main() {
    
        let person = Person::new("Simon");
        let pet = Pet::new("Buddy");
    
        println!("{}'s pets name is {}", get_name(&person), get_name(&pet));
    }
    
    fn get_name<T: A>(a: &T) -> String {
        a.get_name()
    }
    

    Playground

    As a side note.. I have used String here in favor of &str references.. to reduce the need for explicit lifetimes and potentially a loss of focus on the question at hand. I believe it's generally the convention to return a &str reference when borrowing the content and that seems appropriate here.. however I didn't want to distract from the actual example too much.

    0 讨论(0)
提交回复
热议问题