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

5

77

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 structs so that the new() functions of all of them returns an object that they can all be used in the same way without having to write code specific to each one.

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

However, I get the following error:

error[E0562]: `impl Trait` not allowed outside of function and inherent method return types
 --> src/lib.rs:2:17
  |
2 |     fn new() -> impl A;
  |                 ^^^^^^

Is this a limitation of the current implementation of impl Trait or am I using it wrong?

Gallinacean answered 14/9, 2016 at 3:38 Comment(0)
C
21

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.

Concent answered 14/9, 2016 at 3:43 Comment(3)
This isn't the same as returning impl Trait. For instance, you couldn't add a method that in Person returns a Pet but in Pet returns a Person, although both implement A. The RFC (1522) mentions this limitation and expresses a desire to eventually remove it (first bullet under "Initial Limitations").Upas
Honestly I hadn't considered that @trentcl. My answer seems to have helped the OP. How should I proceed given that it is accepted?Concent
@SimonWhitehead I suggested an edit to the first sentence (not sure how the peer review process works exactly, maybe you can see it). But I think your answer is fine and there's no reason it shouldn't be accepted (it solved OP's immediate problem, after all).Upas
W
83

Rust 1.75

As of Rust 1.75 you can use impl Trait as the return type of a trait method:

trait TomorrowToday {
    fn returns_impl_trait(&self) -> impl Iterator<Item = u16>;
}

impl TomorrowToday for u8 {
    fn returns_impl_trait(&self) -> impl Iterator<Item = u16> {
        Some((*self).into()).into_iter()
    }
}

fn main() {
    for v in 7.returns_impl_trait() {
        println!("return_position_impl_trait_in_trait: {v}");
    }
}

However, using impl Trait to fulfill an associated type is still unstable (see below).

Previous versions of Rust

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:

Nightly only

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

//1.78.0-nightly (2024-02-25 0ecbd0605770f45c9151)
#![feature(impl_trait_in_assoc_type)]

trait FromTheFuture {
    type Iter: Iterator<Item = u8>;

    fn returns_associated_type(&self) -> Self::Iter;
}

impl FromTheFuture for u8 {
    type Iter = impl Iterator<Item = u8>;

    fn returns_associated_type(&self) -> Self::Iter {
        std::iter::repeat(*self).take(*self as usize)
    }
}

fn main() {
    for v in 7.returns_associated_type() {
        println!("type_alias_impl_trait: {v}");
    }
}
Woadwaxen answered 14/9, 2016 at 12:40 Comment(4)
Thanks again Shepmaster. I hadn't considered this entirely so I see that my answer doesn't directly address the question. That said, it is accepted now and seems to have helped the OP. How do I proceed from here? Should the question perhaps be edited to remove the specific use of impl Trait or should we work to remove the answer entirely?Concent
@SimonWhitehead I'd be loathe to edit the question so drastically. I think it's fine to leave both answers. You could change your answer to say something like "although you can't do X yet, here's a workaround that might help". Answering the direct question and providing useful alternatives are both valuable contributions. The checkmark mostly means "this answer helped the OP the most". Votes mean "this answer helped me".Woadwaxen
@Cesc the answer was from 2016, but has been updated every few years.Woadwaxen
As of December 28, 2023 when Rust 1.75 is released, this will finally be stable (with some caveats of missing features): blog.rust-lang.org/2023/12/21/async-fn-rpit-in-traits.htmlAccouplement
M
25

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
    }
}
Morven answered 2/4, 2017 at 22:21 Comment(0)
C
21

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.

Concent answered 14/9, 2016 at 3:43 Comment(3)
This isn't the same as returning impl Trait. For instance, you couldn't add a method that in Person returns a Pet but in Pet returns a Person, although both implement A. The RFC (1522) mentions this limitation and expresses a desire to eventually remove it (first bullet under "Initial Limitations").Upas
Honestly I hadn't considered that @trentcl. My answer seems to have helped the OP. How should I proceed given that it is accepted?Concent
@SimonWhitehead I suggested an edit to the first sentence (not sure how the peer review process works exactly, maybe you can see it). But I think your answer is fine and there's no reason it shouldn't be accepted (it solved OP's immediate problem, after all).Upas
S
2

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.

Syzran answered 24/8, 2019 at 20:53 Comment(0)
A
2

Yes, it is, since rust 1.75.0

An example:

  trait T<'a> {
   fn f( &'a self) -> impl Iterator<Item=&'a u64>;
  }

  struct S1<'a> {
   v : &'a mut HashSet<u64>,
  }

  impl<'a> T<'a> for S1<'a> {
   fn f( &'a self) -> impl Iterator<Item=&'a u64> {
    self.v.iter()
   }
  }

  let s1 = S1{ v : &mut HashSet::new() };

  let a1 = s1.f();

  for x1 in a1 { ... }

...
Aeschylus answered 8/1 at 11:30 Comment(0)

© 2022 - 2024 — McMap. All rights reserved.