Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

RFC: Add DerefMove #178

Closed
wants to merge 6 commits into from
Closed
Changes from 1 commit
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
76 changes: 76 additions & 0 deletions active/0000-deref-move.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,76 @@
- Start Date: 2014-07-23
- RFC PR #: (leave this empty)
- Rust Issue #: (leave this empty)

Summary
=======

Add a new lang-item-defined trait, `DerefMove`, which allows moves out of custom
pointer types.

Motivation
==========

There has been some effort to remove special casing from `Box`—[RFC PR
130](https://github.com/rust-lang/rfcs/pull/130) and RFC PRs
[139](https://github.com/rust-lang/rfcs/pull/139) and
[112](https://github.com/rust-lang/rfcs/pull/112) are examples of this trend.
However, there is still one more problem with `Box` that would have to be fixed
for `Box` to be properly special-case-free (bar the construction syntax and
patterns, which is mostly acceptable because of the plan to support `box`
for other pointer types): `Box` pointers can be moved out of, even though
none of the `Deref*` traits support moves.

Detailed design
===============

Add a new trait, `DerefMove`, with a corresponding lang item, `deref_move`, and
define it in `core::ops`:

```rust
#[lang="deref_move"]
pub trait DerefMove<Result> {
/// The method called to move out of a dereference
fn deref_move(self) -> Result;
}
```

The `deref_move` method would be called in any of the following situations:

* Something is being dereferenced, and the only `Deref*` trait it implements is
`DerefMove`;
* Something is being dereferenced, and while it *does* implement `Deref` and/or
`DerefMut`, it also implements `DerefMove` and a value is being moved out of
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I feel like I am missing something in this second bullet: Whether we choose to move or copy a value is based on the static type of that value's expression. If I implement Deref<C> on Foo (for some C:Copy), then *foo will copy rather than move, right? Therefore, this specification seems circular, e.g. if I also implement DerefMove<Box<()>> on Foo as well, then I need to know the type of an expression like *foo to know whether its return value is being copied or being moved, but in order to know what the type of *foo is, I need to know whether I am supposed to be invoking deref or deref_move.

But perhaps I have misunderstood your intention, and there is some other property that you are trying to describe here that is unrelated to the type-based move/copy distinction. And if that is the case, perhaps there is another phrasing that would be clearer here. (I'm just guessing now, but would "if the dereference is in an rvalue context" work?)

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

When I mention a ‘move’ in the RFC, I generally mean a ‘move or copy’. So deref_move is called (if is implemented) whenever a value is moved or copied out of a dereference. This means that if I had a struct, Foo, that implemented Deref<int>, *foo (where foo: Foo) would call deref (because Foo doesn’t implement DerefMove). However, if I implemented DerefMove<uint> on Foo as well, even a copy like *foo would call deref_move, not deref any more.

In other words, deref_move is called if it looks like a move or copy. On types that also implement DerefMove, deref and deref_mut are only called when an expression along the lines of &*p or &mut *p is encountered.

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

That won't work. uint is Copy but Foo probably isn't. It would need to call Deref<int> on Foo and copy out of the resulting &int instead.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

That’s a good point. Maybe the rules could be revised to this:

  1. If the type implements DerefMove but not Deref or DerefMut, all dereferences are done using deref_move.
  2. If a reference is being taken to a dereference (e.g. &*ptr), call deref or deref_mut, depending on the mutability of the reference.
  3. If a value is being dereferenced and implements DerefMove and Deref and/or DerefMut:
    1. If the type is Copy, call deref_move.
    2. Otherwise:
      1. If the type implements Deref<T> where T: Copy, call *deref or *deref_mut.
      2. Otherwise, call deref_move.
  4. If a value is being dereferenced and does not implement DerefMove but does implement Deref and/or DerefMut, use deref or deref_mut.

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

That seems reasonable to me. Presumably if the type implements both Deref and DerefMut, it will choose to use deref instead of deref_mut in 3.ii.a and 4.

the dereference.

This applies to implicit derefences as well.

Remove all special treatment of `Box` by the borrow checker. Instead, `Box`
implements `DerefMove` in the standard library roughly as follows:

```rust
impl<T> DerefMove<T> for Box<T> {
fn deref_move(self) -> T {
let Box(ptr) = self;
std::ptr::read(ptr)
}
}
```

Drawbacks
=========

Adds yet another `Deref*` trait and another lang item, adding complexity to the
language.

Alternatives
============

* Do nothing, and blame `Box`’s special-casing on the fact that it is a lang item
anyway.
* Add a `DerefSet` trait as well, for assignments of the form `*ptr = val`.

Unresolved questions
====================

None.