What does |_: ()| mean?

I'm reading some Rust code on github... :frowning: no idea why this language is becoming more and more popular, worse than Perl...

Ok geniuses what does "|_: ()|" mean?

#[no_mangle]
unsafe fn call_self(arg_len: u32) -> u32 {
    wrap_call(arg_len, |_: ()| STATE.call_self())
}

This is a closure that, when called, takes one argument of type (), which it ignores, and then calls STATE.call_self() and returns its result.

1 Like

This was completely unnecessary.

2 Likes

|x, y| x + y in Rust is a closure (something like an anonymous function) taking two arguments x and y and returning the value x + y. Closure arguments van have optional type annotations, so that might look like |x: i32, y: i32| x + y. If there's only one single argument, the argument list may look like |x: Type|, and if there's none, it might even become || which is also often seen in Rust code.

The () type in Rust is the so-called "unit" type, similar e. g. void in some other languages. It's commonly very useful e. g. as the return type of functions that don't return anything. As a function argument type like here, it is rarely used, unless perhaps if wrap_call is a generic function that allows some sort of argument to be received by the closure optionally, and using the unit type is a way to not make use of that capability.

Function arguments can be patterns and _ is a pattern that matches any value and ignores it instead of introducing a name for the value. Even then, I would have personally probably written the code as |()| STATE.call_self() instead, using the () pattern that matches any "unit" value and will help with type inference just as well as the optional type annotation did. If you wanted to use a not anonymous function, you could also re-write this code as something like

fn callback(_u: ()) -> Foo {
    STATE.call_self()
}
#[no_mangle]
unsafe fn call_self(arg_len: u32) -> u32 {
    wrap_call(arg_len, callback)
}

where in place of Foo the appropriate return type of call_self would be inserted. While closures, using some symbolic syntax, make things a bit more cryptic, (one of) their main use case(s) is to be concise, so thats a trade-off that might be worth it.

3 Likes

This is a general trait pattern used to imitate variadic arguments. For example for ToLuaMulti in mlua - Rust

impl<'lua, T: ToLua<'lua>> ToLuaMulti<'lua> for T // some base types
impl<'lua> ToLuaMulti<'lua> for () // no argument
impl<'lua, A> ToLuaMulti<'lua> for (A,) where A: ToLuaMulti<'lua>, // one argument
impl<'lua, B, A> ToLuaMulti<'lua> for (B, A) // two arguments
where
    B: ToLua<'lua>,
    A: ToLuaMulti<'lua>,
... // more arguments as many as you want to define

then for the callbacks, the trait bounds can be

pub fn call<A: ToLuaMulti<'lua>, R: FromLuaMulti<'lua>>(
    &self,
    args: A
) -> Result<R>

// you can use it as follows
let sum: Function = lua.load(
    r#"
        function(a, b)
            return a + b
        end
"#).eval()?;
assert_eq!(sum.call::<_, u32>((3, 4))?, 3 + 4); // two arguments: `_` is `(i32, i32)`

let sum: Function = lua.load(
    r#"
        function()
            return 1
        end
"#).eval()?;
assert_eq!(sum.call::<_, u32>(())?, 1); // no argument: `_` is `()`

or similar to the closure style in OP

pub fn create_function<'lua, A, R, F>(
    &'lua self,
    func: F
) -> Result<Function<'lua>>
where
    A: FromLuaMulti<'lua>,
    R: ToLuaMulti<'lua>,
    F: 'static + MaybeSend + Fn(&'lua Lua, A) -> Result<R>,

// you can use it as follows

let nop = lua.create_function(|_, _: ()| { // no argument
    Ok(())
});
// equivalent to `function() end` in lua

let greet = lua.create_function(|_, name: String| { // one argument
    println!("Hello, {}!", name);
    Ok(())
});
// equivalent to `function(name) print("Hello, ", name, "!") end` in lua

In case this is more than just a rhetorical question, it's a good thing to learn that when dealing with programming languages, (perhaps apart from any extremes) for real users of a language, the exact details of surface syntax are usually not of particularly high concern. Syntax design is naturally full of arbitrary choices that someone has to make at some point and not everyone will love. I personally don't love Rust's syntax the most either, out of the programming languages I know. The real values of programming languages, and Rust in particular, and the reason why people adopt (and keep using) it, would lie in other things. Like the type system, the language philosophy, unique/interesting useful language features, maybe also tooling and the ecosystem, and so on.

8 Likes

If you can't be bothered to read the language documentation, at least spare us the sarcasm. This doesn't do any good. (Oh, and you are asking people to help you, and you are being sarcastic to them. That's just absurdly egoistic and counter-productive.)

3 Likes

The question has been answered, and there doesn't seem to be much else to add to it other than arguing about subjective readability.

2 Likes