[pre-RFC] linear type modifier

My assumption was that unwinding would hit the Box<T>, which would invoke the destructor of T, which would segfault because it is in an uninitialized state. How would it “know not to”?

(Hence my working assumption has been that the only way to solve this problem in general is for unwinding through an &out to abort - and that this is still worth it. It may be possible to have special-case solutions which avoid the abort, like tying it to Default (or a separate Init trait) which works like the opposite of Drop, and reinitializes the referent of the &out when unwinding hits it.)

Well this compiles http://is.gd/iOLJc2 . So either the problem is solved, or we already have it.

If I’m not misinterpreting your example, then here it’s the stack-allocated Box variable x which is uninitialized. The problematic case would be if the heap-allocated contents of the Box, *x, were uninitialized when unwinding hit it. The compiler has static knowledge of the former, but not the latter. As far as I know there’s no way currently to bring about the second scenario (which is a good thing).

You are right the example is not good enough. Whether or not a box is /borrowed/ is a statically analyzable however. IIRC there are plans to make it so one can move out of a &mut as long as something is moved back in, like http://is.gd/cZxiQ6 (which does not currently compile). borrowing a &out should be no different. Either of these would just require a drop flag in some circumstances.

Yeah, moving out of an &mut conceptually leaves you with an &out.

How could main / Box know in this scenario whether or not *x is initialized? How do you envision the drop flag working? (Where would it go, who would set it?)

I also want things like:

fn emplace_back<'a, T>(vec: &'a mut Vec<T>) -> &'a out T

where it’s only the last element in the Vec that’s uninitialized until the &out obligation is fulfilled.

And:

fn emplace_back_n<'a, T>(vec: &'a mut Vec<T>, num: usize) -> &'a out [T]

here the number of uninitialized elements depends on a runtime value.

It seems like these would require quite sophisticated drop flaggery to tolerate unwinding - likely at an unacceptable performance cost…

The idea is &out can only be eliminated by assignment, in which case you are left with a &mut. If you move it (technically re-borrow and move it) and don’t end up with something with the same lifetime, it must have been written to. That means if you pass &out to a function that returns unit, lets say, the &out becomes a &mut just as if you assigned it yourself.

From an unwinding perspective, the basic thing one needs to know is whether something is borrowed, which is a statically analyzable property. I hope then that in all non-array cases the needed is-borrowed state could somehow be hard coded into the dwarf metadata so as not to occur any overhead unless panicking occurs. In normal operation, the &out must be eliminated before the borrowed thing is dropped, so actually I don’t think a drop flag is needed ever. Note that the same thing could be done for &muts, with mut-borrowed slots never running destructors and unborrowed &muts running them instead, though there is probably no reason to do this other than consistency with &out.

The array case is more complex as you say. Note that a Vec already tracks capacity vs length. It may be sufficient then to wrap the &out ptr in something which will bump the capacity on assignment as a side effect.

Granted this a bit of a cop-out out answer – Vec relies on yet another unsafe implementation detail, and the uninitiated values must be contiguous and at the end.

An interesting thing to note is that it is not allowed to refine an &out with a .field. One would need to pattern match to get an &out to each field of the struct/variant if one wants to fulfill each assign obligation separately – let &out (ref a, ref b) = some_out;. Fine grained borrowing would work on [T; n] more easily than [T], precisely because the former can be pattern matched so much more “thoroughly”.

The borrow checker knows nothing about the structure of heap-allocated data structures. Box is very simple and contains a single value, but you could take out an &out borrow of a single element of an arbitrary complex dynamic structure (HashMap, BTreeMap, …?). How does it know, when unwinding, which element has (or hasn’t) been &out-borrowed? From a borrow checker perspective, if you do foo.get_out_borrow_of_element(bar, baz), I think all that it’s aware of is that foo has been &mut-borrowed. But that’s not nearly enough information.

If you’ve directly &out borrowed something directly visible to the borrow checker on the stack, in that case it could know not to re-run the destructor - but at the inner function where the &out itself is live, you can’t know whether this the case, and where it’s been borrowed from… (and hence, whether it’s safe to let unwinding keep going).

(With respect to refining and &out [T] and so on you might be interested in this comment I made on the RFC a long time ago.)

Where can I find more info about your proposal and the reasons it was rejected? I find it pretty efficient.

@glaebhoerl Sorry. You are right. &out, Drop, and unwinding fundamentally do not get along. Stuff like Vec<&out T> probably means any attempt to statically account for all borrowing is doomed. In general I think taking an &out is only safe if If the “ownership tree”, i.e. all the types that own the T you are taking an &out T and all of the types owned by T, do not implement drop. Vec and Box can give you (encapsulated) &out pointers, but only because their internal structure makes it feasible, and only through unsafe means.

This topic was automatically closed 90 days after the last reply. New replies are no longer allowed.