-
Notifications
You must be signed in to change notification settings - Fork 0
Meeting weekly 2013 07 02
- 0.7 release status
- Fate of @mut
- @ constructor bounds / &-and-dtors
- brson: Struggling to get release candidate tarball presently
- brson: Android build fixed over weekend
- brson: Rusti failures in cross compilation; bots are running 'check-lite' on cross compile
- P: Discussed last week, not enough people to discuss fully
- P: Possibly remove @mut, just use Cell or something
- P: Rationale: the write barriers are pretty much invisible, appear in pattern matches etc, very surprising
- P: Leaning towards just Cell, even though it combines null-ness and borrowed-ness; the effect to the programmer is quite similar: "value not accessible"
- P: if we move this into pure libraries, no compiler support is present at all
- N: there are three approaches: (1.) dynamic borrow, (2.) RAII, (3.) closures, ...
- P: maybe .. there's a sub-topic, the @-constructor
- P: currently there are no restrictions placed on "what can be placed inside @-box". Not true for @Trait and @fn, but normal @ is unconstrained
- P: in particular you can put region pointers inside
- P: this makes it difficult to do a particular pattern with destructors
- N: region analysis only understands lifetime of reachability, assumes that if an @-box goes out of scope it's inaccessible; but if there's a dtor it may still access the &-var during destruction when the GC gets around to it.
- N: our current solution to this problem is to limit what dtors can access. But this is only one way to solve the problem.
- Bblum: you still need some restrictions on dtors even if you fix this
- N: the danger ben is referring to is that if you have 2 objects that refer to one another, ...
//Two objects in the same scope that both have dtors:
{
let object1 = ...;
let object2 = ...;
object1.field = &object2;
object2.field = &object1;
}
//Bad.
- G: I think we're not supposed to permit destructors on cyclic values at all, no?
- N: if we prohibit & and @ in objects with dtors, that's true.
- N: but it rules out some desirable things. it would be nice to be able to do:
fn foo(rwarc: &Rwarc<T>) {
let write_lock = rwarc.write_lock();
write_lock.contents = foo;
}
(the above example is using RAII to automatically release the write_lock.) (RWARC already uses unsafe destructors like this; this would just allow the implementation to avoid #[unsafe_destructor].)
- N: Similarly, the use of
Cell
would be the same. - P: this is not a theoretical limitation, this is something we run into all the time in Servo due to use of custom smart pointers for the DOM.
- P: the way we avoid this currently is to use closures, but that produces lots of rightward drift
- N: in case it's not obvious, the RAII-object needs to hold an &-ptr on the lock
- N: so, suppose we take advantage of lifetimes: it's ok for objects with dtors to have &-ptrs, but they must be longer than the object lifetime itself.
- G: yeah, that's what I'd be suggesting.
let val1 = ...;
let val2 = HasDtor { v1: &val1 }; // illegal
- P: everyone's surprised that you can put & in @
- B: I still don't see why we need it
- (G: I don't know either!)
- N: partly, it takes extra type rules to make it not-work, and it seems harmless
- N: I think the other use-cases are marginal, and you can get by with ~
- N: eg. sometimes you want a function that returns an @Trait that closes over &-ptrs, say. Like in the case of iterators say, or parser combinators. But it only really applies when you're trying to hide implementation details, and in those cases you can use a ~Trait.
// Example that is not totally unreasonable:
fn mk_reader<'a>(s: &'a str) -> @Reader:'a {
BufReaderImpl {s: s} as @Reader:'a
}
// But it can also be written:
fn mk_reader<'a>(s: &'a str) -> BufReaderImpl<'a> {
BufReaderImpl {s: s}
}
// Or it could be written, if you really wanted to hide impl details:
fn mk_reader<'a>(s: &'a str) -> ~Reader:'a {
~BufReaderImpl {s: s} as ~Reader:'a
}
- G: ok so the only set of cases you have in mind are those with @Trait (since they need bounds)?
- N: Somewhat. Consider second example above. Reveals implementation details, but also in some ways preferable: no heap allocation, can inline, etc.
- N: Using ~ loses the aliasability of @, but you can also just borrow the ~ to get aliasability.
// Legal today:
fn foo<A>(x: A) -> @A { @x }
// Illegal under this proposal, so you would need
// 'static to guarantee that type A doesn't have borrowed
// pointers, and hence can be managed:
fn foo<A:'static>(x: A) -> @A { @x }
- G: so .. to get a bit concrete, what's being proposed?
- N: 1. limit what goes in a @box, require any &-ptrs to be 'static
- N: 2. modify dtors to permit &-ptrs in the destructed object whose lifetime is strictly greater than the lifetime of the object.
- Bblum: this sounds surprising. You could define an object that has a &'self ptr in it that can never be instantiated.
- N: the only situation that would be ruled out would be if you want something with a dtor that points to something you declared earlier in the same block, and you want dtor to run on exit from the block.
// Would not type check because `lock` contains a pointer to
// `locked`, and that has the same time as lock:
{
let locked = RwArc { ... };
let lock = locked.write_lock();
lock.foo;
}
// Would need to introduce a block:
{
let locked = RwArc { ... };
{
let lock = locked.write_lock();
...;
}
}
- G: how much work would it be to modify lifetime rules so that first works? just compiler munging, right? If we're trying to copy C++ idioms, we should try to aim for #1 there.
- N: no theoretical difficulties, just have to tweak how hierarchy is defined
- G: this seems reasonable to me, I tend to have trouble comprehending the use cases for borrowed pointers in managed data anyhow.
- N: another advantage of this is that it absorbs a number of use cases for once fns. Not the task once-fns but the closure kinds. No rightward drift.
- P: yeah, writing the closures we're writing in servo is ... (rightward drift?) Also slightly worried about compile performance.
- N: ok, so sounds like generally indifferent-to-positive opinions?
- F: so we wouldn't have lifetime bounds on @Trait anymore?
- N: I hadn't planned to change how lifetime bounds work, you could write it it would just probably be an error further on. ~Trait:'lt still makes sense.
- N: Cf. exmple above. If you really wanted to hide implementation detail, not sure how else you'd do it. I guess you could take a closure.
- J: is the mechanism that would be used to constrain the contents of @Trait objects also be applicable to user-defined smart-pointers?
impl<A:'static> GC<A> {
fn new(a: A) -> GC<A> { ... }
}
- P: With that decided, we could replace the methods for accessing @mut concents with an RAII system, like:
// Long hand access:
{
let as_mut = box.as_mut();
as_mut.field = ...;
}
// I think this would also work:
box.as_mut().field += 1;
- P: to be concrete:
let box: @Cell<int> = ...;
box.as_mut().value = 10;
-
G: does this have to be an
@Cell
? (i.e. One could just write "Cell", right?) -
N: Yeah, this is like the old
Mut<>
structure. ... some discussion... -
N: yeah, what does
as_mut
return? -
P: some kind of & that can set the write barrier flag?
-
P: has a pointer to the value and pointer to the write barrier
-
P: flips the write barrier back on destruction ...
-
P: want a mechanism for a managed pointer to a field of a managed object.
-
G: Sorry to keep foot-dragging but .. I don't understand how
Cell
actually works (also it's pretty difficult to take notes given that state!)
struct MutCell<'a, T> {
priv cell: &'a Cell,
value: &'a mut T
}
impl<T> Cell<T> {
fn as_mut(&'a self) -> MutCell<'a, T> {
self.lock_for_mutability();
MutCell {cell:self, value: transmute(&self.data)}
}
}
impl<'a> Drop for MutCell<'a> {
fn drop() {
self.cell.unlock_from_mutability();
}
}
- G: this says nothing about GC
- N: the borrow checker will guarantee that the @-box is rooted for the lifetime of any &-ptr pointing into it
- G: To what extent does this feel like pushing unsoundness to a library? This is the same as what we have now?
- N: Same but more flexible, more explicit, and less magic
- S: feels less magical. It's documented and such. In a library. Obvious there are possible failure modes.
- P: in order to trip the write guard, you have to call a function. You might actually read the docs for that function.
- N: also integrates somewhat better with multiple smart pointer types. If you wind up with multiple smart pointers that have a mut and non-mut variant, they all have to support this functionality and do it correctly.
- B: Cell is a nice name, but it's kinda weird that this has nullability.
- G: if it's mutable, can't we say Cell<Option> covers nullability?
- P: we could take out the nullability of Cell today.
- G: this doesn't make it much bigger, right?
- N: there's probably some overhead
- B: wouldn't want to change all existing code that does use nullability
- N: question: does everyone think Cell will continue to be useful?
- P: it's probable / possible that nullable-cell will not be useful.
- G: Why do we think the usefulness of it will change?
- N: it's mostly used to work around problems with missing once functions when writing tasks etc.
- Bblum: there's a use-case involving 2 closures that might take the cell, but not both.
- G: 2 mins left. Maybe naming? Cell and NullCell? Cell and Cell? Maybe just remove nullability "someday in the future"?