Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Create variant without parameter from an empty tuple

Tags:

rust

I want to be able to construct enum values by giving the variant constructor a value:

use self::Variants::*;

#[derive(Clone, Debug)]
enum Variants {
    Unit(()),
    One(u8),
}

fn emit<C: Fn(S) -> T, S, T>(constructor: C, value: S) -> T {
    constructor(value)
}

fn main() {
    let unit: Variants = emit(&Unit, ());
    println!("{:?}", unit); // Prints Unit(()).
    let one: Variants = emit(&One, 10);
    println!("{:?}", one); // Prints One(10).
}

The issue with this example is that I need () in Unit(()) instead of the more normal Unit (without parameter).

I tried using specialization:

#![feature(specialization)]

trait Constructor<S, T> {
    fn construct(self, value: S) -> T;
}

impl<T> Constructor<(), T> for T {
    fn construct(self, _value: ()) -> T {
        self
    }
}

impl<F: Fn(S) -> T, S, T> Constructor<S, T> for F {
    default fn construct(self, value: S) -> T {
        self(value)
    }
}

fn emit<C: Constructor<I, R>, I, R>(callback: &C, value: I) -> R {
    callback.construct(value)
}

use self::Variants::*;

#[derive(Clone, Debug)]
enum Variants {
    Unit,
    One(u8),
}

fn main() {
    let unit: Variants = emit(&Unit, ());
    println!("{:?}", unit); // Should prints Unit.
    let one: Variants = emit(&One, 10);
    println!("{:?}", one); // Prints One(10).
}

but this fails at compile time with:

conflicting implementations of trait `Constructor<(), _>`:

From my understanding of the RFC, this fails because no one of these impls is a subset of the other. I think it is because the T in for T of the first impl is more generic than the F of the for F of the second implementation. Hence, it cannot be the specialization.

On the other hand, it cannot be the general (default) implementation because Constructor<(), T> for T (and even Constructor<S, T> for T) is more specific than Constructor<S, T> for F because T is written twice in the former.

This is my understanding from reading the RFC. Please tell me if I'm wrong.

How could I make the first example work without having to give a useless parameter to Unit?

I am open to solutions that require the nightly compiler.

like image 239
antoyo Avatar asked Jul 12 '26 09:07

antoyo


1 Answers

What about implementing Fn for Variants? (Note: Fn requires FnMut and FnMut requires FnOnce, so we have to implement all three.)

#![feature(fn_traits)]
#![feature(unboxed_closures)]

use self::Variants::*;

#[derive(Clone, Debug)]
enum Variants {
    Unit,
    One(u8),
}

impl FnOnce<((),)> for Variants {
    type Output = Variants;

    extern "rust-call" fn call_once(self, args: ((),)) -> Self::Output {
        self.call(args)
    }
}

impl FnMut<((),)> for Variants {
    extern "rust-call" fn call_mut(&mut self, args: ((),)) -> Self::Output {
        self.call(args)
    }
}

impl Fn<((),)> for Variants {
    extern "rust-call" fn call(&self, _: ((),)) -> Self::Output {
        self.clone()
    }
}

fn emit<C: Fn(S) -> T, S, T>(callback: &C, value: S) -> T {
    callback(value)
}

fn main() {
    let unit: Variants = emit(&Unit, ());
    println!("{:?}", unit); // Prints Unit.
    let one: Variants = emit(&One, 10);
    println!("{:?}", one); // Prints One(10).
}

The only weird aspect of this solution is that you could now pass values such as &One(10) as the first argument to emit (though the second argument could only be ()).

like image 119
Francis Gagné Avatar answered Jul 14 '26 20:07

Francis Gagné



Donate For Us

If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!