Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Does type constructor implement Fn?

Tags:

rust

I am not sure that the title of my question is correct since I am not sure where exactly placed. Let say I have a code which looks like:


struct MyWrapper(u64);

fn my_func<F>(f: F, n: u64) -> MyWrapper
where
    F: Fn(u64) -> MyWrapper,
{
    f(n)
}

fn main() {
    my_func(MyWrapper, 3);
}

It compiles and works so it looks like MyWrapper implements trait Fn.

However, should I try to use it in a trait.

struct MyWrapper(u64);

trait MyTrait
where
    Self: Fn(u64) -> MyWrapper,
{
}

impl MyTrait for MyWrapper{}

I get an error

16 | impl MyTrait for MyWrapper{};
   |      ^^^^^^^ expected an `Fn<(u64,)>` closure, found `MyWrapper`
   |
   = help: the trait `std::ops::Fn<(u64,)>` is not implemented for `MyWrapper`

It was a more theoretical question.

Speaking practicaly, what I am trying to achieve is to implement trait like this

Edit: I have rightfully pointed out that my example is not full, so there is a fixed version.

pub enum Status {
    New,
    Cancelled,
}

struct NewTransaction(u64);

struct CancelledTransaction(u64);

fn get_by_status(id: &str, status: Status) -> Result<u64, ()> {
    Ok(3)
}

pub trait Transaction
where
    Self: std::marker::Sized,
{
    const status: Status;
    fn get(id: &str) -> Result<Self, ()>;
}

impl Transaction for NewTransaction {
    const status: Status = Status::New;
    fn get(id: &str) -> Result<Self, ()> {
        get_by_status(id, Self::status).map(Self)
    }
}

impl Transaction for CancelledTransaction {
    const status: Status = Status::Cancelled;
    fn get(id: &str) -> Result<Self, ()> {
        get_by_status(id, Self::status).map(Self)
    }
}

This code compiles, but as you can see - all implementations of Transaction for every type are exactly the same, so it seems totally reasonable to move this implementation as a default. Like this

pub trait Transaction
where
    Self: std::marker::Sized,
{
    const status: Status;
    fn get(id: &str) -> Result<Self, ()> {
        get_by_status(id, Self::status).map(Self)
    }
}

impl Transaction for NewTransaction {
    const status: Status = Status::New;
}

impl Transaction for CancelledTransaction {
    const status: Status = Status::Cancelled;
}

And here I got a complain that Self cannot be used as a Value. I've tried to fix it by introducing a condition where Self: Fn(u32) -> Self on trait but it didn't work either.

Edit: In the end, I implemented the idea suggested by Sven Marnach - added a method new and required all struct to implement this method. It still looks quite strange, since the implementation is exactly the same for all structures, but it works.


pub trait Transaction
where
    Self: std::marker::Sized,
{
    const status: Status;
    fn new(n: u64) -> Self;
    fn get(id: &str) -> Result<Self, ()> {
        get_by_status(id, Self::status).map(Self::new)
    }
}

impl Transaction for NewTransaction {
    const status: Status = Status::New;
    fn new(n: u64) -> Self {
        Self(n)
    }
}

Thanks everyone for answers!

like image 599
Max Avatar asked Jul 17 '19 19:07

Max


2 Answers

The constructor of a tuple-like struct or enum variant is actually treated as a function name when used in a context where a value rather than a type is expected, and it is treated as the type it names in a context where a type is expected.

When calling my_func(MyWrapper, 3), the name MyWrapper denotes a function with a function item type that coerces to the function pointer type fn(u64) -> MyWrapper. In particular, the item type implements the trait Fn(u64) -> MyWrapper.

In the code impl MyTrait for MyWrapper {}, however, MyWrapper denotes the struct type it declares. That type is completely different from the type of MyWrapper when used in a value context, and it does not implement the Fn(u64) -> MyWrapper trait.

In your actual use case, I believe the easiest solution is to require a new() method with the desired prototype on the type:

trait Payment {
   const status: Status;
   fn new(x: u64) -> Self;
   fn get(id: u64) -> Result<Self, Error> {
       get_by_status(Self::status, id).map(Self::new)
   }
}

Implementors of Payment will only need to provide new() method with the desired prototype, but will inherit the default implementation of get().

like image 77
Sven Marnach Avatar answered Nov 05 '22 11:11

Sven Marnach


It compiles and works so it looks like MyWrapper implements trait Fn.

A quick and dirty way to know the type of something in Rust is to do:

struct MyWrapper(u64);

fn main() {
    let mut foo = MyWrapper;
    foo = ();
}

This produces an error:

error[E0308]: mismatched types
 --> src/main.rs:5:11
  |
5 |     foo = ();
  |           ^^ expected fn item, found ()
  |
  = note: expected type `fn(u64) -> MyWrapper {MyWrapper}`
             found type `()`

As you can see foo is not a MyWrapper structure so MyWrapper does not implement Fn like you thought it would.

I agree this can be confusing, see tuple struct case:

A struct expression with fields enclosed in parentheses constructs a tuple struct. Though it is listed here as a specific expression for completeness, it is equivalent to a call expression to the tuple struct's constructor. For example:

struct Position(i32, i32, i32);
Position(0, 0, 0);  // Typical way of creating a tuple struct.
let c = Position;  // `c` is a function that takes 3 arguments.
let pos = c(8, 6, 7);  // Creates a `Position` value.

Speaking practically, what I am trying to achieve is to implement trait like this

Your example is not complete so my answer is not final but I don't think it's possible to do what you want.

like image 28
Stargateur Avatar answered Nov 05 '22 12:11

Stargateur