glib

Macro closure

Source
closure!() { /* proc-macro */ }
Expand description

Macro for creating a Closure object. This is a wrapper around Closure::new that automatically type checks its arguments at run-time.

A Closure takes Value objects as inputs and output. This macro will automatically convert the inputs to Rust types when invoking its callback, and then will convert the output back to a Value. All inputs must implement the FromValue trait, and outputs must either implement the ToValue trait or be the unit type (). Type-checking of inputs is done at run-time; if incorrect types are passed via Closure::invoke then the closure will panic. Note that when passing input types derived from Object or Interface, you must take care to upcast to the exact object or interface type that is being received.

Similarly to clone!, this macro can be useful in combination with signal handlers to reduce boilerplate when passing references. Unique to Closure objects is the ability to watch an object using the #[watch] attribute. Only an Object value can be passed to #[watch], and only one object can be watched per closure. When an object is watched, a weak reference to the object is held in the closure. When the object is destroyed, the closure will become invalidated: all signal handlers connected to the closure will become disconnected, and any calls to Closure::invoke on the closure will be silently ignored. Internally, this is accomplished using Object::watch_closure on the watched object.

The #[weak], #[weak_allow_none], #[strong], #[to_owned] captures are also supported and behave the same as in clone!, as is aliasing captures via rename_to. Similarly, upgrade failure of weak references can be adjusted via #[upgrade_or], #[upgrade_or_else], #[upgrade_or_default] and #[upgrade_or_panic].

Notably, these captures are able to reference Rc and Arc values in addition to Object values.

⚠️ IMPORTANT ⚠️

glib needs to be in scope, so unless it’s one of the direct crate dependencies, you need to import it because closure! is using it. For example:

use gtk::glib;

§Using as a closure object

use glib_macros::closure;

let concat_str = closure!(|s: &str| s.to_owned() + " World");
let result = concat_str.invoke::<String>(&[&"Hello"]);
assert_eq!(result, "Hello World");

§Connecting to a signal

For wrapping closures that can’t be sent across threads, the closure_local! macro can be used. It has the same syntax as closure!, but instead uses Closure::new_local internally.

use glib;
use glib::prelude::*;
use glib_macros::closure_local;

let obj = glib::Object::new::<glib::Object>();
obj.connect_closure(
    "notify", false,
    closure_local!(|_obj: glib::Object, pspec: glib::ParamSpec| {
        println!("property notify: {}", pspec.name());
    }));

§Object Watching

use glib;
use glib::prelude::*;
use glib_macros::closure_local;

let closure = {
    let obj = glib::Object::new::<glib::Object>();
    let closure = closure_local!(
        #[watch] obj,
        move || {
            obj.type_().name()
        },
    );
    assert_eq!(closure.invoke::<String>(&[]), "GObject");
    closure
};
// `obj` is dropped, closure invalidated so it always does nothing and returns None
closure.invoke::<()>(&[]);

#[watch] has special behavior when connected to a signal:

use glib;
use glib::prelude::*;
use glib_macros::closure_local;

let obj = glib::Object::new::<glib::Object>();
{
    let other = glib::Object::new::<glib::Object>();
    obj.connect_closure(
        "notify", false,
        closure_local!(
            #[watch(rename_to = b)]
            other,
            move |a: glib::Object, pspec: glib::ParamSpec| {
                let value = a.property_value(pspec.name());
                b.set_property(pspec.name(), &value);
            },
        ),
    );
    // The signal handler will disconnect automatically at the end of this
    // block when `other` is dropped.
}

§Weak and Strong References

use glib;
use glib::prelude::*;
use glib_macros::closure;
use std::sync::Arc;

let closure = {
    let a = Arc::new(String::from("Hello"));
    let b = Arc::new(String::from("World"));
    let c = "!";
    let closure = closure!(
        #[strong] a,
        #[weak_allow_none]
        b,
        #[to_owned]
        c,
        move || {
            // `a` is Arc<String>, `b` is Option<Arc<String>>, `c` is a `String`
            format!("{} {}{}", a, b.as_ref().map(|b| b.as_str()).unwrap_or_else(|| "Moon"), c)
        },
    );
    assert_eq!(closure.invoke::<String>(&[]), "Hello World!");
    closure
};
// `a`, `c` still kept alive, `b` is dropped
assert_eq!(closure.invoke::<String>(&[]), "Hello Moon!");