Pre-RFC: "leak" scope created with brackets

Currently, we may initialize a struct with some feature, and use it later. In this case, we must write very ugly code like:

// init foo
#[cfg(feature="foo")]
let foo_builder=...;
#[cfg(feature="foo")] // now we have to duplicating the #[cfg(feature="foo")] since we cannot use brackets
let foo=foo_builder.build();
// ...
// deal with foo
#[cfg(feature="foo")] { // using brackets removed a duplicated `#[cfg(feature="foo")]`
    let result = foo.use_it_later();
    deal_with(result);
}

A more beautiful code might use brackets to mark what #[cfg(feature="foo")] affected:

#[cfg(feature="foo")] { // init foo
    let foo_builder=...;
    let foo=foo_builder.build();
}
// ...
#[cfg(feature="foo")] { // deal with foo
    let result = foo.use_it_later();
    deal_with(result);
}

But, such code failed to compile, since foo dropped immeditely after the bracket is closed.

Further, when dealing with unsafe, same things happened again, we have to wrote:

let result = unsafe {unsafe_fun()};

rather than

unsafe {let result = unsafe_fun()}

If we have a sequence of unsafe function, we have to wrote either

let result1=unsafe{unsafe_fun_1()};
let result2=unsafe{unsafe_fun_2()};
let result3=unsafe{unsafe_fun_3()};
let result4=unsafe{unsafe_fun_4()};
let result5=unsafe{unsafe_fun_5()};

or

let (result1, result2, result3, result4, result5) = unsafe {
    let result1=unsafe_fun_1();
    let result2=unsafe_fun_2();
    let result3=unsafe_fun_3();
    let result4=unsafe_fun_4();
    let result5=unsafe_fun_5();
    (result1, result2, result3, result4, result5)
}

Considering this, I want to propose a new attribute, #[leak]

This attribute could apply to any block, leak all of the variables defined with let out of the block.

With such option, we may wrote more clearly:

#[leak, cfg(feature="foo")] { // init foo, leak foo and foo_builder outslde the brackets.
    let foo_builder=...;
    let foo=foo_builder.build();
}
// since `foo` is leaked, we could call `foo.*` directly.
// ...
#[cfg(feature="foo")] { // deal with foo
    let result = foo.use_it_later();
    deal_with(result);
}
{
    #[leak] unsafe {
        let result1=unsafe_fun_1();
        let result2=unsafe_fun_2();
        let result3=unsafe_fun_3();
        let result4=unsafe_fun_4();
        let result5=unsafe_fun_5();
    } // all the 5 results are leaked thus it is usable outside this unsafe block
} // here, all the 5 results are dropped.

Further discussion : fine grained control with leak

  1. Could the leak attribute accept several variables that indicate which variable should be leaked?
#[leak(foo), cfg(feature="foo")] { // init foo, leak foo and foo_builder outslde the brackets.
    let foo_builder=...;
    let foo=foo_builder.build();
}
foo.usable(); // foo is defined
// foo_builder is not leaked
  1. Is it possible to leak with if clauses

The following code seems clearly enough, but is it worth implement it?

#[leak(foo,bar)]
if flag {
    let foo=baz.foo();
    let bar=foo.bar();
} else {
    let bar=baz.bar();
    let foo=bar.foo();
}

I have no idea about the name of the attribute, I tried things like expand extend flatten ... Thus if you have a better name, tell me please:)

A macro can easily solve this:

macro_rules! multi_cfg {
    {
        $( #[$cfg:meta] )*
        {}
    } => {
    };
    {
        $( #[$cfg:meta] )*
        {
            $first_stmt:stmt;
            $( $rest_stmts:stmt; )*
        }
    } => {
        $( #[$cfg] )*
        $first_stmt;
        
        multi_cfg! {
            $( #[$cfg] )*
            {
                $( $rest_stmts; )*
            }
        }
    };
}

fn main() {
    multi_cfg! {
        #[cfg(not(feature = "foo"))]
        {
            let bar = 123;
        }
    }
    dbg!(bar);
}

"leak" means https://doc.rust-lang.org/std/boxed/struct.Box.html#method.leak to me, so I'm not a fan of that word.

But you'll probably be interested in Rust Temporary Lifetimes and "Super Let" - Mara's Blog

Another one:

let (result1, result2, result3, result4, result5);
unsafe {
    result1 = unsafe_fun_1();
    result2 = unsafe_fun_2();
    result3 = unsafe_fun_3();
    result4 = unsafe_fun_4();
    result5 = unsafe_fun_5();
}
3 Likes

This is also a trivial macro_rules! macro for the non-unsafe case:

macro_rules! syntax_group {
    ($($tt:tt)*) => { $($tt)* };
}

#[cfg(feature="foo")]
syntax_group! { // init foo
    let foo_builder=...;
    let foo=foo_builder.build();
}

// ...

#[cfg(feature="foo")]
syntax_group! { // deal with foo
    let result = foo.use_it_later();
    deal_with(result);
}

I've used this before, but mostly when unstable syntax is present and needs to be isolated from the stable compiler. For other cases, I mostly prefer using more of a "null object" pattern where the dispatch code still runs but just doesn't do anything without the feature.

4 Likes