It seems I need to double the brackets: is this correct?
No: double brackets are that you chose to use tuples and calling a function that takes a tuple requires you to create a tuple first, but you can have locks that take several arguments, for example F: Fn(i32, i32)
. That is, this function can be written like this:
fn iterNeighbors<F>(i: i32, j: i32, f: F) where F: Fn(i32, i32) { f(i-1, j); f(i+1, j); f(i, j-1); f(i, j+1); }
However, it seems that saving this set makes sense for this case.
I think I'm trying my best to get the arguments back into life correctly, because they rotate in a recursive call. How should I annotate the lifetime if I want s2 to be freed immediately before returning, and I want s1 to survive either on return or in a recursive call?
There is no need for links (and therefore no need for resources), just pass the data directly:
fn nthLoop(n: i32, s1: HashSet<(i32, i32)>, s2: HashSet<(i32, i32)>) -> HashSet<(i32, i32)> { if n==0 { return s1; } else { let mut s0 = HashSet::new(); for &p in s1 { iterNeighbors(p, |p| { if !(s1.contains(&p) || s2.contains(&p)) { s0.insert(p); } }) } drop(s2); // guarantees timely deallocation return nthLoop(n-1, s0, s1); } }
The key point here is that you can do everything by value, and everything that is passed by value will, of course, contain its values.
However, this does not compile:
error: cannot borrow data mutably in a captured outer variable in an `Fn` closure [E0387] if !(s1.contains(&p) || s2.contains(&p)) { s0.insert(p); } ^~ help: see the detailed explanation for E0387 help: consider changing this closure to take self by mutable reference iterNeighbors(p, |p| { if !(s1.contains(&p) || s2.contains(&p)) { s0.insert(p); } })
That is, the closure tries to change the values that it fixes ( s0
), but the closure property Fn
does not allow this. This trait can be invoked in a more flexible way (when used together), but it imposes more restrictions on what closure can do internally. (If you're interested, I wrote more about this: http://huonw.imtqy.com/blog/2015/05/finding-closure-in-rust/ )
Fortunately, there is an easy fix: using the FnMut
attribute, which requires that a closure can only be called when it has unique access to it, but allows internal elements to mutate things.
fn iterNeighbors<F>((i, j): (i32, i32), mut f: F) where F: FnMut((i32, i32)) { f((i-1, j)); f((i+1, j)); f((i, j-1)); f((i, j+1)); }
The caller will look something like this:
The values also work here: returning the link in this case will return a pointer to s0
, in which the stack frame will be stored, which will be destroyed as the function returns. That is, the link points to dead data.
The fix does not use links:
fn nth(n: i32, p: (i32, i32)) -> HashSet<(i32, i32)> { let s0 = HashSet::new(); let mut s1 = HashSet::new(); s1.insert(p); return nthLoop(n, s1, s0); }
This works if I set the closure manually, but I cannot figure out how to cause the closure. Ideally, I would like to set up a static dispatch.
(I don’t understand what this means, including the compiler error messages you came across helps us help you.)
In addition, I only used HashSet in F # because I assume that Rust does not provide a purely functional set with efficient set-theoretic operations (union, intersection, and difference). Am I right in assuming that?
Depending on what you want, no, for example. both HashSet
and BTreeSet
provide various set-theoretic operations as methods that return iterators .
Some small points:
- The explicit / named lifetime allows the compiler to talk about the static validity of the data, they do not control it (i.e., allow the compiler to indicate when you are doing something wrong, but the language still has the same static usage / life guarantee resource as C ++)
- the version with the loop is likely to be more efficient, as it is written, since it reuses memory directly (replacing sets plus
s0.clear()
, however, the same advantage can be realized with the recursive version by passing s2
for reuse, rather than to remove it. while
can be for _ in 0..n
- there is no need to skip closures by reference, but with or without a link, there is a static dispatch (closing is a type parameter, not an object-object).
- conditionally, the closing arguments are the last and are not accepted by reference, because it simplifies the determination and transmission of their embedded lines (for example,
foo(x, |y| bar(y + 1))
instead of foo(&|y| bar(y + 1), x)
) The return
keyword is not required for returned returns (if ;
omitted):
fn nth(n: i32, p: (i32, i32)) -> HashSet<(i32, i32)> { let s0 = HashSet::new(); let mut s1 = HashSet::new(); s1.insert(p); nthLoop(n, s1, s0) }