Callback with compound arguments

I am trying to understand how to implement a structure with a callback. I succeeded to do it with primitive data types of arguments, but I have a difficult time doing it for compound data types in arguments.
My code:


fn main()
{
  let renderer = Render::new( "name1".to_string() );
}

pub struct UpdateEvent
{
  pub dt : f64,
}

//

pub struct Render< OnUpdate >
where
  OnUpdate : Fn( &mut UpdateEvent ),
{
  pub name : String,
  pub f_on_update : OnUpdate,
}

//

impl< OnUpdate > Render< OnUpdate >
where
  OnUpdate : Fn( &mut UpdateEvent ),
{

  pub fn new( name : String ) -> Render< OnUpdate >
  {
    let f_on_update = | e : &mut UpdateEvent | {};
    Render { name, f_on_update }
  }

}

It throws 2 errors:

error[E0284]: type annotations needed: cannot satisfy for<'r> <_ as FnOnce<(&'r mut UpdateEvent,)>>::Output == ()
cannot satisfy for<'r> <_ as FnOnce<(&'r mut UpdateEvent,)>>::Output == ()

error[E0308]: mismatched types
expected type parameter OnUpdate, found closure

I would appreciate it If someone would point me where to look or give a hint.

Your code is saying that the caller of Render::new can decide what type OnUpdate should be but you also force OnUpdate to be the type of a specific closure. You probably want something like dyn Fn( &mut UpdateEvent) rather than OnUpdate.

2 Likes

Than you @lemmih. Non-generic solution with pointers Box< dyn Fn( &mut UpdateEvent ) > works. But what about generic solution? Does such exist?

It's a bit strange but you can be generic on the type of callable. But if you're supporting closures, it's rare to distinguish between the type of callable. You couldn't put the examples in a Vec for instance, as they have different types. You'll monomorphize based on the types in generic contexts. Etc.

I suggest using Box<dyn FnMut> and have Render be not-generic.

2 Likes

Making this closure into a function pointer is possible because it doesn't capture any environment

wait what? I thought closures were sugar for structs+methods.. even if that struct doesn't capture anything, a pointer to that struct is not exactly a pointer to its method?

Or is it that in, say, C terms, because the struct doesn't contain anything the address of the method is the same as the struct itself?

Or should I just not think about it, and tuck away the idea that closures that don't capture their environment can be treated as function pointers because that's just the way it is in Rust?

I would love an IDE plugin that would let me hover over any function (incl closures) and see all the Fn traits (and fn pointer) it can satisfy. Could this one day be done in something like rust-analyzer? Would be an amazing teaching tool too. I find grasping closures to be up there with traits and the borrow checker for difficulty onboarding to Rust

1 Like

Non capturing closures can be coerced to fn pointers.

https://doc.rust-lang.org/reference/type-coercions.html

2 Likes

I don't know how it's implemented in the compiler, but I basically think about it this way.

  • Closures that capture something...
    • And consume what they capture are FnOnce, like a fn(self, ...)
    • And modify what they capture are FnMut, like a fn(&mut self, ...)
    • And don't modify what they capture are Fn, like a fn(&self, ...)
    • And all of these may have a size as they carry around their captured data

While:

  • Closures that don't capture can be considered like a fn(&self) too, but...
    • You never actually use self
    • And it's a zero-sized singleton like ()
    • Which could likewise be created from nowhere / doesn't need to be carried around
    • So might as well be considered to be like a fn() instead, other than needing a handle (the anonymous closure type) to be able to "find" it
    • And can thus be freely coerced to a function pointer
3 Likes

Non-capturing closures are zero-sized structs, so pointer to them doesn't make much sense - they are purely compile-time concept, a type with attached method, which is called everywhere the type is used. And, if something expects function pointer - compiler can automatically use the pointer to this attached method.

3 Likes

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.