Thanks for all the responses!

I knew there was something simple I could do in my specific case - in this
case, `.take()` or more generically `utils::replace`. Thanks for the
pointers.

That said, the general problem remains. I think it goes as follows:

- There is a container accessible from some root pointer.
- One burrows a mutable pointer to some entry nested in the container.
- At this point, we can split all the memory reachable from the root
pointer into three kinds:
  A. The mutable entry data we burrowed a pointer to.
  B. The data along the path leading from (including) the root pointer to
the mutable entry.
  C. Other data reachable by the container.

For example, if I have a `&mut T` to the `i`th entry of a `~[T]` array,
then:
  A. Is the `&mut T` and anything reached from it.
  B. Is the pointer to the base of the array (the `&mut T` is that base
plus `i` * the size of `T`).
  C. Is all the data contained in all the entries other than the `i`th, and
anything reached from them.

Now, there are three kinds of mutations one can do to the container - these
that mutate A, B or C data.

Burrowing a 2nd mutable pointer to A data is forbidden, to avoid two
mutable pointers to the same data. So far, so good.

Burrowing a mutable pointer to B data is forbidden, because if it mutates
than the entry we have a pointer to might be disconnected from the
container or compromised in another way.

Burrowing a mutable pointer to C data should be allowed, as it doesn't
overlap with our mutable pointer in any way.

For example, one could reasonably expect to be able to burrow a mutable
pointer to both `vec[0]` and `vec[1]` at the same time.

However, the type system can't tell (in general) whether a specific mutable
operation on the container touches only B date or also C data.  It doesn't
know that `vec.push()` might change the base pointer (B data) but `vec[1]`
doesn't. All it can see is that there is mutable access in both case, so it
plays it safe.

It is hard to do better. Even in the simple vector case. Suppose I get two
parameters, `i` and `j`, and I tryu to get `&mut T` for both `vec[i]` and
`vec[j]`. This is OK as long as i is different from j. If they are equal,
this is forbidden. But there's no way to make these kinds of decisions in
the type system - this requires a run-time check.

It might be able to do somewhat better by using nested lifetimes. I'm not
certain this can be done in a general way using the current type system,
though. But even with nested lifetimes, this wouldn't solve the general
problem.

When the static type system fails, one must rely on either unsafe code
("trust me") or a dynamic type system ("verify me"). The latter may be
expensive - in fact, supporting "verify me" might require extra cost for
code that has no need for this feature (to make relevant meta-data
available and up-to-date).

Right now, Rust goes along the way of "trust me". That said, there are
idioms (like `take`) which can hide the nastiness in their inside. Perhaps
we need a cheat cheat of common use cases and the patterns for achieving
them using the current mechanisms (that is, libraries that provide support
for the common use cases). ARC for example, but also `take` and `replace`
and I guess a few more besides. Then we could say "if you feel none of them
is enough, define a new useful generic safe mechanism (with unsafe
internals) to cover the case"; but in general all containers code should
not include any `unsafe` blocks, it should just use one of the
debugger/approved standard library "trust me" functions (which would
presumably be well-debugged).

This would be quite a project, though... even when based on existing code.
_______________________________________________
Rust-dev mailing list
Rust-dev@mozilla.org
https://mail.mozilla.org/listinfo/rust-dev

Reply via email to