If I am writing a long function, I often want to close off a chunk of code with { code_in_here } so that local variables I declare within the chunk don't "escape". Example:
fn long_function() {
let mut x = 1;
let y = 3
{
let y = 2;
println!("I have {} apples");
}
assert!(y == 3);
}
However, inside the { }, it's still possible to read (and even write to) x. When reading long functions, I would like a way of assuring that a given complicated chunk of code only touches the variables I want it to touch.
A solution:
fn long_function() {
let x = 1;
let y = 3
{fn t(x: &i32) {
let y = 5;
println!("I have {} apple and {} oranges.", x, y);
}t(&x);}
assert!(y == 3);
}
This works, and does exactly what I need, but is rather ugly. I suggest a bit of built-in sugar for this use case. Perhaps rather than:
{fn t(args: &T, to: &mut U, pass: &V) {
//isolated logic
}t(&args, &mut to, &pass);}
we would have:
close (&args, &mut to, &pass) {
// enclosed logic, assured not to touch anything but args, to and pass
}
And we can tell just by looking at one line exactly what variables this chunk of code touches and what its side effects are.
...or even better, a closure with controlled access to its enclosing scope that returns a value:
let a: i32 = close (&args, &mut to, &pass) -> i32 {
// enclosed logic, assured not to touch anything but args, to and pass
to = args + pass;
let some_int = 5 - args;
some_int
}
You'd do this to improve readablity / auditability, so you'll frequently prefer the nested function call with a descriptive name and good comments. Also a macro could build code like:
{ fn tempname<A,B,C>(name1:A, name2: B, name3: C) {
} tempname(name1,name2,name3) }
A macro can't do this.
fn item.fn signature requires full knowledge of all types.Edit: Wait a second. I think I have a (terrible) idea...
Well, okay. I thought I had a prototype, but it barely does any of what I wanted it to:
fn pointer).let x;)&T or &mut Tfn pointer type and a closure)let a = vec![()];
let b = vec![()];
let mut c = vec![()];
let d = vec![()];
let mut e = vec![()];
using!{[b, mut c, ref d, ref mut e] {
let _ = b; // b is Vec<()>. It was moved.
c.sort(); // c is Vec<()>. It was moved.
let _ = d.iter(); // d is &Vec<()>
let _ = e.iter_mut(); // e is &mut Vec<()>
// let _ = a.iter(); // a cannot be used.
}}
Sigh. The only way to make it work really nice is with language support. There's simply no other way to check off the first two bullets.
Cute trick with the closure type inference rules.
An RFC for closure style type inference for _ types in nested fns makes this trivial, except not the first two points. And sounds vastly more useful.
fn bar() {
..
fn foo(x: _) { .. }
..
}
It's also much more convincing since you merely argue that nested fns should benefit the same ergonomics that closures do.
I still think: You're doing this for readability, so go the whole way and split up the long function or at least give your nested functions good names.
As an aside, if you want truly hackish then one could maybe do this latex style with a procedural macro that produced side effectual debug builds that wrote the type information to an .aux file using std::intrinsics::type_name :)
You can always just write a clippy lint that enforces the behaviour you want. It'll essentially come down to
#[clippy::close(mut x, move y)]
{
// modifying x and y possible within block. Everything else can only be read
// can modify Cell/Mutex though
}
Restricting the language is exactly what lints do. Especially our restriction lints
@burdges when I factor out a function and give it a name, I'd rather it serve a purpose beyond just being a line drawn in the sand for readability. The reason for this is that, in code that is subject to frequent changes in requirements:
And thus factoring out a function to make the code cleaner in the short term can backfire in the long run, if done arbitrarily.
Oh, um, er... I think I misread your posts and didn't realize you were still advocating nesting the function body inside the original function.
In that case, my major annoyances are really
fn foo(x: _)!)I do think nested fn foo(x: _) sounds like an uncontroversial RFC, since closures already work that way.
#[clippy::close(mut x, move y)] looks a lot like a similar Ada/SPARK feature.
Most helpful comment
You can always just write a clippy lint that enforces the behaviour you want. It'll essentially come down to
Restricting the language is exactly what lints do. Especially our
restrictionlints