-
Notifications
You must be signed in to change notification settings - Fork 1.6k
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
Change &
to be a borrow operator.
#248
Conversation
Change the address-of operator (`&`) to a borrow operator. This is an alternative to rust-lang#241 and rust-lang#226 (cross-borrowing coercions). The borrow operator would perform as many dereferences as possible and then take the address of the result. The result of `&expr` would always have type `&T` where `T` does not implement `Deref`.
Repurposing It also breaks the following symmetry: If value There are too many surprises. If we do need a borrow operator, it should be another sigil. But I still believe a semi-explicit coercion operator is a better solution. I'll try to address the |
Actually I think it is doable with my
|
I think that this additional complexity may be worth it. I recently spent some time explaining Rust's ownership semantics, borrowing rules, and syntax to a relatively new user. The trickiest thing to explain was not the rules of ownership, but what is going on with Having |
Even better, we can enable both prefix and postfix variants of
We can in fact adopt both proposal. |
@reem, And having Let's use |
It wouldn't just confuse C / C++ programmers. It would make Rust less usable as a systems language and would break generics. This is the kind of operator overloading trickery that people look down on C++ for. |
With & as the borrow operator, the symmetry is now not syntactic, but semantic, and I think that's better. Rather than & always being the address of something, it becomes always the borrowed form of something, which is rather more significant in Rust. For instance, in implementing Hyper we have needed a way to represent an immutable view into Headers. Normally, this can be done through This is a general problem - often times you do not want the address of something, you want an immutable or mutable borrow of it. @thestinger, you could always just use &(thing) to get the actual address, so I'm not sure why this makes Rust less usable as a systems language. The interaction with generics needs to be explored further. |
Needing to use |
This does make conversions from |
Sounds potentially unsafe in combination with FFI code #[repr(C)]
struct X {
i: i16,
j: i8,
k: i8,
}
impl Deref<i8> for X {
fn deref(&self) -> &i8 {
&self.k
}
}
fn main() {
let x = X { i: 0, j: 0, k: 0 };
let _: *const i16 = &x as *const _ as *const _;
} |
@mahkoh You'd use |
@reem: The need to use |
I'd say just the fact that Also, the borrow operator is actually a syntax sugar, so it should not supersede the more fundamental operation that is taking the address. |
@reem: If the code above compiles then it stores an invalid pointer in _. |
@mahkoh could you explain why please? The |
@CloudiDust the The principle here is that in Rust, borrowing is actually a more fundamental operation than taking the address. (I'm not sure I agree 100% with this principle, but I am warming to it). |
@mahkoh It doesn't seem like that is a new issue. I understand that this makes it slightly more error prone and I dislike that, but I think that the advantages in ergonomics for many extremely common types possibly outweighs the downsides. |
|
@nick29581 Maybe I'm misunderstanding something but
|
@mahkoh it seems the problem here is the cast, not the & operator |
@thestinger would you feel better about using a new operator? Leave |
A possibility if we were to use |
@reem with |
@nick29581: I'm happy with how it works today... :P I'm strongly against changing it because I think it's already the sanest solution and I don't think we need another operator. Obscure operators are really syntactic salt rather than sugar; most people don't like sigils. |
The downsides are undefined behavior and segfaults (best case.) If you want to make both things safe at the same time you have to disallow the wildcard in How would this work with types that deref to themselves? |
@thestinger fair enough. There does seem to be motivation for sugaring ref/deref somewhat though - |
@mahkoh I don't think having a borrow operator will lead to any more segfaults/undefined behaviour than the existing set of operators, in particular for your example, I don't see things getting worse just because you type one less The question about types which deref to themselves is an interesting one! I expect we would have to detect that statically and forbid using the borrow operator with such types. |
@nick29581 I think every piece of FFI code would have to use |
@thestinger I think the plan for unification would be:
@reem The problem is that "borrow" implies only one level of indirection, it simply means "borrow/take the address of the argument", and it doesn't mean "borrow the content in the most often used way". Though tempting, for different containers, the ways would be different, and they are hard to generalize into a single unary operator. But this operator will work for pointer-like types as those types have the same use pattern. |
I don't see the need for another operator. Why does everyone want to make the language into a complicated mess? There's no appreciation for orthogonality and simplicity at all in the discussions on all of these proposals. There's only the desire to keep adding every feature under the sun. Language design is as much about which features are left out but Rust's community doesn't seem to get that. It's headed full force ahead to being a more complicated beast than both C++ and Scala and I seem to be the only person who cares. |
@thestinger Well personally I don't mind writing Anyway this is personal preference, and syntax sugar can either be added now or later, or never. |
Absolutely like the proposal. I don't care for confusion of C/C++ programmers. A good C/C++ programmer typically has enough technical background and understanding to get a new concept. But the goal should be to lower the barriers of entry for everyone. I very much prefer more convenience around borrowing to "non-confusion of group X". |
@buster Even if we don't care about C/C++ programmers (we should, the principle of least surprise and all that, let alone the fact they are our main target audience), using And I don't see how the concept of "magic borrow" (not "borrow" in the "take a reference to" sense, but the magic borrow proposed in this RFC proper) is easier to grasp for a programmer who has never been exposed to Rust or other systems programming language, as "magic borrow" or not, we'll have to tell him/her what a reference is first, anyway. And just saying "use the borrow operator" without an explanation doesn't seem like a good way to teach Rust, a systems language. |
@buster: Adding more complexity is not reducing the barrier to entry. The issue isn't that it would cause confusion for C++ programmers. It would make low-level code harder to write and would add more confusing magic to the language. It wouldn't result in any significant improvements to convenience. |
I do agree that with the proposed changes writing borrowing code can be much easier. However, I'd say that reading code with explicit dereferences for borrows is much more informative about what the code is actually working on. For method calls, implicit dereference is great, but there you have the context of the method name you call. With a borrow operator, you have much less context. |
…nge the discussion to focus more on smart pointers than the Deref trait.
With the borrow operator here and
Honestly I think the situation is sub-optimal. We need a coherent solution. |
I don't think |
I believe the advantages of this approach vs an implicit coercion are: | ||
|
||
* better integration with type inference (note no explicit type in the above | ||
example); |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Your proposal doesn't seem to do any inference at all because it simply derefs as much as possible without looking at the context in which the reference is use. Proposal #241 on the other hand does look like it's doing type inference and the only reason you have to use y: &T
in the example above is that y
isn't constrained further. I'd expect let y = x;
to create a &Rc<T>
as expected and
fn foo(x: &Rc<T>) {
bar(x);
}
fn bar(x: &T) { }
to just work.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
That's correct, as long as there is some more context to infer the type from. In practice you sometimes need to give the inferencer some hints
Rust is a safe language and sometimes more verbose than other languages to make this possible. One of the few places where Rust is not safe is FFI code and every change that makes unsafe code even less safe and even less predictable should have a very good justification. The rust repo contains over 452k lines of Rust code (including comments) and only 1.7k lines (0.38%) contain "&", over 1k of them in librustc (111k LOC.)⁂ This "borrow problem" seems to be nothing but a mild inconvenience. &** looks a bit ridiculous but that's really all there is to it. ⁂ When you remove librustc and only leave the libraries you can see that &* occurs about once every 430 lines. |
@mahkoh, thanks for the statistics. It seems that the need of the borrow operator is not that urgent. And After I noticed that #235 has yet another different take on the meaning of On the other hand, the autoderef problem in |
|
@nick29581: I'm using &mut Vec quite often to have multiple functions push into the same vector, but maybe that's just me. |
Three definitions of borrow, Deref for String and Vec, and thestinger seems to be the only person who cares.. |
I'm also not a fan of this plan and would rather just have cross-borrowing an similar magical behavior go away at least for 1.0, but I don't really have any better arguments than thestinger, and me grouching on every RFC that I don't think adds more convenience than complexity doesn't help anyone. If the core team wanted an opinion poll on RFCs they'd probably use a more appropriate platform for that than pull request comments. |
@45ujksy5l03jcvf, well I think many cares. Reading through the recent RFCs and having some thoughts makes me wonder whether we are increasing language complexity for questionable gain, and whether we are dealing with complexity by piling up more complexity, because we asked the wrong question. And I'll admit maybe I've fallen victim to the temptation of feature creep. I'll try to get out. :) |
I think the following three questions are related:
#245, this, the coercion proposals and the autoderef problem of |
@CloudiDust |
@45ujksy5l03jcvf, in case it's not clear, I was agreeing with @thestinger too. :) I think your definition of I think part of the problem is, we cannot come up with a better name for the new concept so |
Some data and a little discussion: https://gist.github.com/nrc/809614adb2bbb38232b7 |
Closing - we don't have time to do this pre-1.0 (and we're not sure if we even want to) and it would be too big a breaking change for post-1.0. |
Change the address-of operator (
&
) to a borrow operator. This is analternative to #241 and #226 (cross-borrowing coercions). The borrow operator
would perform as many dereferences as possible and then take the address of the
result. The result of
&expr
would always have type&T
whereT
does notimplement
Deref
.