Require Zero-Sized Type

@pinbender Aren't you really looking for function types?

pub struct Widget {
    // Uppercase Fn() is a function trait, lowercase fn() is a specific
    // type for functions that are not associated with data
    pub foo: fn(u64) -> u64,
    pub bar: fn(bool) -> bool,
}

pub const MY_WIDGET: Widget = Widget {
    foo: |x| x + 1,
    bar: |b| !b,
};

#[test]
pub fn widget_test() {
    assert_eq!((MY_WIDGET.foo)(1), 2);
    assert_eq!((MY_WIDGET.bar)(true), false);
}

I could implement it that way, which has some advantages. I had always been a bit hazy on the fn/Fn distinction, so if fn() implies that it's not carrying a closure, that makes it more interesting than I had originally considered.

The disadvantages there would relate to ergonomics of defining the function object instances and figuring out how to juggle the lifetimes.

At this point I'm using the type system to identify the function group, which makes my API kind of nice in some ways, and a little inconvenient in others. I'll have to consider the function object idea some more. Thanks for the suggestion.

1 Like

Easy rule: try to write it as a top-level pub fn ..(..) -> .. { .. }, then it should fit into the respective fn() type. As far as I understand, they're the same as function pointers in C.

Do your methods/functions have lifetime or type parameters?

No, I mean the lifetime of the Widget in your example.

Ah! You can derive Copy on this Widget, so lifetimes shouldn't be a problem at all.

Copying more than one pointer per widget is likely to be a performance bottleneck in my application. I'm expecting millions of function invocations per second to be required, a significant portion of which will cross thread boundaries. Keeping the value type to the size of a single pointer is going to make a difference.

So a manual vtbl struct was suggested by @withoutboats in the very first reply of this thread :slight_smile:

Why would these vtbl structs need to be copied around? Iā€™d assume they would all be statics or even consts. Receivers of it would be taking (using the Widget example) a &'static Widget.

At this point, perhaps you can consider using associated consts with a trait, where the consts return fn pointers. If all your dispatch is statically known, then you can do something like:

trait AllConst {
   const FOO: fn(u64) -> u64;
   const BAR: fn(bool) -> bool;
}

fn call_foo<A: AllConst>(x: u64) -> u64 {
    A::FOO(x)
}

I have to say that itā€™s a bit hard to suggest meaningful things in this thread because the problem is being viewed through a microscope and itā€™s unclear what the big picture is. Weā€™re examining a bunch of trees - whatā€™s the forest? :slight_smile:

Also, if youā€™re looking at 1M+ calls/sec/cpu, you may want to avoid cross-CPU communication anyway. But, as mentioned, itā€™s hard to say because there isnā€™t enough detail (and I realize you didnā€™t ask for suggestions on that part specifically, but itā€™s apparently an implementation consideration). So, Iā€™d suggest to provide more context (if you can) to the problem - you may get better and more appropriate suggestions.

1 Like

shrug It's possible I was in a different mood, or that @bgeron 's example added enough context to make it seem more interesting.

I would probably want to be using &'static Widget, yes. Like I said, this will take more consideration to decide whether or not it will fit in what I'm trying to do. My guess at this point is that I'll probably just stick with the stateless trait idea, since I have that working, and it's exactly what I wanted in the first place. (Minus the compiler guarantee.)

Honestly, the most useful comment was that constraining to a zero sized type isn't supported. It really could have been left at that.

2 Likes

Surely you didnā€™t expect such a peculiar request to not pique interest :slight_smile: But ok, fair enough - sometimes you just want an objective answer and not opinions/suggestions.

2 Likes

Thanks for sticking with me here, but I've encountered a new wrinkle in the discussion.

I was curious about poking around at how the vtable technique might work out in another area of my code that needed something similar, and ran into a bit of a roadblock. Basically, I can't figure out how to get this to work with type parameters to generate vtables.

A heavily distilled version of the problem looks somewhat like this:

struct Vtbl {
    number: fn() -> u32,
}

trait MakeANumber {
    fn number(&self) -> u32;
}

#[derive(Default)]
struct NumberMaker;
impl MakeANumber for NumberMaker {
    fn number(&self) -> u32 { 42 }
}

fn make_vtable_for<T: MakeANumber + Default>() -> &'static Vtbl {
    static VTABLE : Vtbl = Vtbl {
        number: || {
            let maker : T = Default::default();
            maker.number()
        }
    };
    &VTABLE
}

This leads to an E0401.

I'm sure this is somehow coming from a mismatch between my understanding of how this should work, and how it does actually work. (Unfortunately, I have a lot of experience with C++ templates, where a pattern like this could be made to work. Obviously rust generics are not C++ templates.)

Ultimately, the only way around this issue that I've been able to come up with is exposing this implementation detail in the external API, so the trait would have to be able to return the vtable. This is a generated vtable though, and the details of the functions in question are kind of specific to the implementation per-type, so that's sort of... not ideal.

Any suggestions?

1 Like

A similar issue was recently discussed here. In short, I don't think there's any good way to do this because, as the error explains, inner types don't inherit the type of the outer function; it's merely a visibility/privacy effect.

Can you ditch the Vtbl struct and instead use associated consts? I don't really know what you're doing in the rest of the code, but something like this perhaps:

trait MakeANumber {
    const NUMBER: fn() -> u32;
}

struct NumberMaker;
impl MakeANumber for NumberMaker {
    const NUMBER: fn() -> u32 = || 42;
}

// dispatch through the const
fn foo<T: MakeANumber>() -> u32 {
    T::NUMBER()
}

// get a reference to the fn
fn get_fn<T: MakeANumber>() -> fn() -> u32 {
    T::NUMBER
}

I need dynamic dispatch in this case, so associated constants won't work. A reference to the functions in question need to go into a buffer for later use.

1 Like

Can you elaborate on the scenario some more? How are you making these functions?

I have an API performing an operation per-type. Internally, it does some work, and stores a collection of type-specific functions in a queue for later processing. I was hoping to generate a vtable pointer to store in that queue. As a result of discovering that I can't generate that vtable at the per-type call site, I resorted to generating it via a dynamic trait implemented by a generic struct, and extracting the vtable from a trait object. (A solution I consider to be less desirable than support for nested generic type access.)

It's probably worth mentioning that my current issue is not actually the bit that originally spawned this discussion thread. It's a coincidence that I also needed a vtable for this part of the system.

1 Like

So i guess I still donā€™t quite understand why associated consts wouldnā€™t work. A caller into your api is providing a generic type that impls some trait, or no? This is your per-call specific type, or so it sounds. Why is an fn pointer on that trait, as an associated const, not workable? Thatā€™s basically a poor manā€™s vtbl that you explicitly define via associated consts.

Also, why canā€™t you store closures in the queue?

I don't want to pay the cost of boxing (i.e. no closures), and I need more than one function per entry, but don't want to store more than one pointer in the queue.

How about a static vtbl pointer (reference) via associated const:

struct Vtbl {
   x: fn() -> u32,
}

trait MakeANumber {
    const VTBL: &'static Vtbl;
}

struct NumberMaker;
impl MakeANumber for NumberMaker {
    const VTBL: &'static Vtbl = &Vtbl { x: || 42};
}

MakeANumber is a public trait, and the vtable is a private implementation detail, so the vtable can't be part of it.

Ok, I give up :slight_smile:

Using the compiler to get you a vtbl ptr from a trait object doesnā€™t sound horrible given lack of alternatives and your requirements. Iā€™d be interested myself if anyone has better suggestions.

3 Likes

:smiley: Thanks for sticking with me on the suggestions.