Hello! I have three mostly-unrelated questions that all pertain to the interactions between lifetime dependencies / conditionals / reassignment / inout argument dependence. I thought that I would group them here, rather than make separate posts. Any help is appreciated—feel no pressure to respond to all three questions simultaneously.
(1) inout argument dependence behaving like conditional reassignment
In the WIP Swift Evolution proposal for lifetimes, it is stated that
inoutargument dependence behaves like a conditional reassignment
I am curious to know why inout argument dependence always behaves like conditional reassignment. Specifically, there are some functions where the reassignment always happens, meaning the conjoined dependence is an unnecessary overapproximation. As an example,
@_lifetime(x: copy y)
func reassign(_ x: inout Span<Int>, _ y: Span<Int>) {
x = y;
}
The inout argument dependence here could act like unconditional reassignment, instead of implicitly acting more like @_lifetime(x: copy x, copy y).
I am also not quite sure if this is changing—when trying things out with a nightly build of the compiler, this behavior was not the case (i.e. this in fact behaved like @_lifetime(x: copy y) without the implicit copy x). But this behavior also appeared to be buggy, so I am not sure what the intended design here is.
Is this a language design question, or is the documented behavior necessary? I would imagine that the non-overapproximating alternative would be technically sound, possible to implement, just maybe not particularly useful. But my perspective here is not the most informed.
(2) Lifetime dependent variables escaping their scope
When reassigning lifetime-dependent variables in branches of a conditional, errors about escaping are raised inconsistently. I am curious about what the sound, intended behavior is.
func test() {
var arr = [1, 2, 3];
var span: Span<Int>! = nil;
// this will always work, regardless of whether `arr` is `let` or `var`
do {
span = arr.span
}
// this will give an error when arr is `var` but NOT when it is `let`
// error: lifetime-dependent variable `span` escapes its scope.
if (Bool.random()) {
span = arr.span;
} else {
span = arr.span;
}
// ensure that span is not implicitly dropped
print(span[0]);
}
I would expect the example above to be accepted, regardless of whether arr is let or var. If this is the case, I am more than happy to file an issue on GitHub with this behavior.
More generally, I would expect that lifetimes to be adjusted by taking the least upper bound of the lifetime information in the two branches. For instance, I would imagine that in the following example, usage of span after the conditional would require read access to both arr1 and arr2.
if (Bool.random()) {
span = arr1.span;
} else {
span = arr2.span;
}
Does this seem reasonable? If so, does the implementation align with this? I tried testing it out, but I couldn’t find a way to test this behavior without running into the var behavior noted above.
(3) Ternary expressions and borrowing
It appears that the “branches” of a ternary expression will consume those expressions. As an example,
func bor(it: borrowing MutableSpan<Int>) { }
func test(s1: borrowing MutableSpan<Int>,
s2: borrowing MutableSpan<Int>,
b: Bool) {
// works
if (b) {
bor(it: s1)
} else {
bor(it: s2)
}
// error: s1 is borrowed and cannot be consumed
bor(it: b ? s1 : s2)
}
My (evidently faulty) mental model for this is that the bor function requests its argument to be able to be borrowed, and that either branch of the ternary can be borrowed, so this should be okay. But, I’d imagine that the reason that this does not work is that the ternary operator itself consumes both of the expressions in its branches, making this get rejected.
Can expressions just not be borrowed in this way? I believe that once the Borrow type + accessors land, this should be able to work as follows (using syntax I’m mostly making up):
bor(it: borrow (b ? Borrow(s1) : Borrow(s2))
Does this seem plausible? Will there be language-level support (rather than library level through a Borrow type) for borrowed expressions like this? Am I just totally misunderstanding this situation?
Again, any and all thoughts on one or many of these questions would be appreciated.