Typestate with functions of the same name?

This function won't compile, because the fn new is defined twice:

use std::marker::PhantomData;

trait Selector {}

struct SelectA;
impl Selector for SelectA {}

struct SelectB;
impl Selector for SelectB {}

trait Thing {
    type Selector: Selector;
}

struct Foo<T: Thing> {
    phantom: PhantomData<T>,
}

impl<T> Foo<T>
where
    T: Thing<Selector = SelectA>,
{
    pub fn new() -> Self {
        todo!()
    }
}

impl<T> Foo<T>
where
    T: Thing<Selector = SelectB>,
{
    pub fn new() -> Self {
        todo!()
    }
}

I was able to get it to compile with this, but I don't like it, the code looks dirty:

use std::{any::Any, marker::PhantomData};

pub trait Selector {
    type This: Selector<This = Self>;
    fn this() -> &'static Self::This;
}

struct SelectA;
impl Selector for SelectA {
    type This = SelectA;
    fn this() -> &'static Self {
        &Self
    }
}

struct SelectB;
impl Selector for SelectB {
    type This = SelectB;
    fn this() -> &'static Self {
        &Self
    }
}

trait Thing {
    type Selector: Selector;
}

struct Foo<T: Thing> {
    phantom: PhantomData<T>,
}

impl<T> Foo<T>
where
    T: Thing,
    <<T as Thing>::Selector as Selector>::This: 'static,
{
    pub fn new() -> Self {
        let this = T::Selector::this() as &dyn Any;
        if this.downcast_ref::<SelectA>().is_some() {
            todo!();
        } else if this.downcast_ref::<SelectB>().is_some() {
            todo!();
        } else {
            unreachable!();
        }
    }
}

And this will work in the case where the fn params are identical in both cases, but if I want.the same function name but with different params, this hack won't work.

i am aware of the runtime implications of downcast. But the point here is not to avoid a single branch, its to be able to provide an API which the caller can not misuse.

What do you think? Am I missing something obvious?

Why not

use std::marker::PhantomData;

trait Selector {}

struct SelectA;
impl Selector for SelectA {}

struct SelectB;
impl Selector for SelectB {}

trait Thing {
    type Selector: Selector;
}

struct Foo<T: Thing> {
    phantom: PhantomData<T>,
}

trait FooNewSelector {
    fn new_foo<T: Thing<Selector = Self>>() -> Foo<T>;
}
impl FooNewSelector for SelectA {
    fn new_foo<T: Thing<Selector = Self>>() -> Foo<T> {
        todo!()
    }
}
impl FooNewSelector for SelectB {
    fn new_foo<T: Thing<Selector = Self>>() -> Foo<T> {
        todo!()
    }
}

impl<T> Foo<T>
where
    T: Thing,
{
    pub fn new() -> Self
    where
        T::Selector: FooNewSelector,
    {
        T::Selector::new_foo()
    }
}

(or, if Selector itself ought to be coupled with Foo, and all Selectors support Foo::new, you could make this new_foo part of Selector, too)


I hadn’t read this part this yet. As long as the number of params is identical, a trait-based approach should work the same though.

1 Like

And if number of params is different then two more braces would help, anyway.

Sure, it looks a tiny bit funny to write Foo::new(()) or Foo::new((1,2,3)), but it works and it's shorter than macros would be.

1 Like

This topic was automatically closed 90 days after the last reply. We invite you to open a new topic if you have further questions or comments.