::with_locals

Let's start with a basic example: returning / yielding a format_args local.

```rust use ::core::fmt::Display; use ::with_locals::with;

[with('local)]

fn hex (n: u32) -> &'local dyn Display { &format_args!("{:#x}", n) } ```

The above becomes:

```rust use ::core::fmt::Display;

fn withhex (n: u32, f: F) -> R where F : FnOnce(&' dyn Display) -> R, // for<'local> F : FnOnce(&'local dyn Display) -> R, { f(&format_args!("{:#x}", n)) } ```

f: F, here, is called a continuation: instead of having a function return / yield some element / object, the function takes, instead, the "logic" of what the caller would have liked to do with that element (once it would have received it), so that it is the callee who handles that object instead.

By shifting the logic like so, it is the callee and not the caller who runs that logic, which thus happens before the callee returns, so before it cleans its locals and makes things that refer to it dangle.

This is the whole point of all this strategy!

Now, to call / use the above function, one can no longer bind the "result" of that function to a variable using a let binding, since that mechanism is reserved for actual returns, and the actual code running in the caller's stack.

Instead, one calls / uses that with_hex function using closure / callback syntax:

rust,ignore with_hex(66, |s| { println!("{}", s); })

This is extremely powerful, but incurs in a rightward drift everytime such a binding is created:

rust,ignore with_hex(1, |one| { with_hex(2, |two| { with_hex(3, |three| { // ughhh .. }) }) })

Instead, it would be nice if the compiler / the language provided a way for let bindings to magically perform that transformation:

rust,ignore let one = hex(1); let two = hex(2); let three = hex(3);

Operating in this fashion is called Continuation-Passing Style, and cannot be done implicitly in Rust. But that doesn't mean one cannot get sugar for it.

Enters #[with]!

```rust,ignore

[with] let one = hex(1);

[with] let two = hex(2);

[with] let three = hex(3);

```

When applied to a function, it will tranform all its so-annotated let bindings into nested closure calls, where all the statements that follow the binding (within the same scope) are moved into the continuation.

Here is an example:

```rust

use ::withlocals::with; #[with] fn hex (n: u32) -> &'ref dyn ::core::fmt::Display { &formatargs!("{:#x}", n) }

#

[with]

fn hexexample () { let s: String = { println!("Hello, World!"); #[with] let shex = hex(66); println!("shex = {}", shex); // Outputs s_hex = 0x42 let s = shex.tostring(); asserteq!(s, "0x42"); s }; asserteq!(s, "0x42"); } ```

The above becomes:

```rust

use ::withlocals::with; #[with] fn hex (n: u32) -> &'ref dyn ::core::fmt::Display { &formatargs!("{:#x}", n) }

# fn hexexample () { let s: String = { println!("Hello, World!"); withhex(66, |shex| { println!("shex = {}", shex); // Outputs s_hex = 0x42 let s = shex.tostring(); asserteq!(s, "0x42"); s }) }; assert_eq!(s, "0x42"); } ```

Trait methods

Traits can have #[with]-annotated methods too.

```rust,ignore

use ::with_locals::with;

# trait ToStr { #[with('local)] fn tostr (self: &' Self) -> &'local str ; } ```

Example of an implementor:

```rust

use ::withlocals::with; trait ToStr { #[with] fn tostr (self: &'_ Self) -> &'ref str ; }

# impl ToStr for u32 { #[with('local)] fn tostr (self: &' u32) -> &'local str { let mut x = *self; if x == 0 { // By default, the macro tries to be quite smart and replaces // both implicitly returned and explicitly returned values, with // what the actual return of the actual with_... function must // be: return f("0");. return "0"; } let mut buf = [b' '; 1 + 3 + 3 + 3]; // u32::MAX ~ 4000000000 let mut cursor = buf.len(); while x > 0 { cursor -= 1; buf[cursor] = b'0' + (x % 10) as u8; x /= 10; } // return f( ::core::str::fromutf8(&buf[cursor ..]) // refers to a local! .unwrap() // ); } }

#[with]

fn main ()

{

let s: &'ref str = 42.to_str();

assert_eq!(s, "42");

}

```

Example of a user of the trait (≠ an implementor).

```rust

use ::withlocals::with; trait ToStr { #[with] fn tostr (self: &'_ Self) -> &'ref str ; }

# impl ::core::fmt::Display for _ { #[with] // you can #[with]-annotate classic function, // in order to get the let assignment magic :) fn fmt (self: &' Self, fmt: &'_ mut ::core::fmt::Formatter<'>) -> ::core::fmt::Result { // You can specify the // special lifetime instead of applying [with] // vvvv let s: &'ref str = self.0.tostr(); fmt.writestr(s) } } // (Using a newtype to avoid coherence issues) struct _(T); ```

See examples/main.rs for more detailed examples within a runnable file.

Usage and the "Special lifetime".

Something important to understand w.r.t. how #[with] operates, is that sometimes it must perform transformations (such as changing a foo() call into a with_foo(...) call), and sometimes it must not; it depends on the semantics the programmer wants to write (that is, not all function calls rely on CPS!).

Since a procedural macro only operates on syntax, it cannot understand such semantics (e.g., it is not possible for a proc-macro to replace foo() with with_foo() if, and only if, foo does not exist).

Because of that, the macro expects some syntactic marker / hints that tell it when (and where!) to work:

  1. Obviously, the attribute itself needs to have been applied (on the enscoping function):

    ```rust,ignore

    [with('special)]

    fn ... ```

  2. Then, the macro will inspect to see if there is a ["special lifetime"] within the return type of the function.

    ```rust,ignore // +-------------+ // | | // -------- V

    [with('special)] // vvvvvvvv

    fn foo (...) -> ...'special... ```

    That will trigger the transformation of fn foo into fn with_foo, with all the taking-a-callback-parameter shenanigans.

    Otherwise, it doesn't change the prototype of the function.

  3. Finally, the macro will also inspect the function body, to perform the call-site transformations (e.g., let x = foo(...) into with_foo(..., |x| { ... })).

    These transformations are only applied:

Remarks

Advanced usage

If you are well acquainted with all this CPS / callback style, and would just like to have some sugar when defining callback-based functions, but do not want the attribute to mess up with the code inside the function body (i.e., if you want to opt-out of the magic continuation calls at return sites & co.),

then, know that you can:

Example

```rust use ::core::fmt::Display; use ::with_locals::with;

[with(continuationname = return)]

fn displayaddr (addr: usize) -> &'ref dyn Display { if addr == 0 { return!( &"NULL" ); } withhex(addr, |hex| { return(&format_args!("0x{}", hex)) }) } // where

[with]

fn hex (n: usize) -> &'ref dyn Display { &format_args!("{:x}", n) } ```

Powerful unsugaring

Since some statements are wrapped inside closures, that basic transformation alone would make control flow statements such as return, ?, continue and break to stop working when located in the scope of a #[with] let ... statement (after it).

```rust,compilefail use ::core::fmt::Display; use ::withlocals::with;

[with]

fn hex (n: u32) -> &'ref dyn Display { &format_args!("{:#x}", n) }

fn main () { for n in 0 .. { // <- break cannot refer to this: with_hex(n, |s| { // === closure boundary === println!("{}", s); // ^ Error! if n >= 5 { // | break; // ------------+ } }) } } ```

And yet, when using the #[with] let sugar the above pattern seems to work:

```rust use ::core::fmt::Display; use ::with_locals::with;

[with]

fn hex (n: u32) -> &'ref dyn Display { &format_args!("{:#x}", n) }

[with]

fn main () { for n in 0 .. { #[with] let s = hex(n); println!("{}", s); if n >= 5 { break; } }; } ```

Debugging / Macro expansion

If, for some reason, you are interested in seeing what's the actual code generated / emitted by a #[with] attribute invocation, then all you have to do is to enable the expand-macros Cargo feature:

```toml [dependencies]

...

with_locals = { version = "...", features = ["expand-macros"] } ```

This will display the emitted code with a style very similar to cargo-expand, but with two added benefits: