A possibly more ergonomic syntax for borrow


#1

As I understand it, the borrowing functionality comes down to four main versions
show - let another look but not change
loan/borrow - let another change, but you can only look
share - both can change
copy - we both end up with unique versions

Is there any reason we can’t do something like [loan]xyz or [show]abc instead of the &, and &mut? this seems clearer when reading the code and should be no different for the compiler

Edit: At least for me as a beginner in rust coming from a scripting/sysadmin background, the & is confusing to me


#2

other possible formats
&loan abc
loan abc
abc [loan]

show / peek
loan / borrow

examples:

fn main (){
    let mut book1 = 1;
    let mut book2 = 10;
    let mut book3 = 100;
    let mut book4 = 1000;
    example_borrower(loan book1);
    example_sharer(share book2);
    existing_sharer(&mut book3);
    example_peeker(show book4);
}
fn example_borrower(borrow book1){ book1++; }
fn example_sharer(share book2){ book2++; }
fn existing_sharer(&mut book3){ book3++; } // I'm not sure if the syntax here is correct
fn example_peeker(peek book4){ println!("Behold: {}!",book4); }

#3

internals.rust-lang.org is generally the place you want to go when making suggestions.

In my opinion, this is a bad idea, because it alienates existing users and C users, and because it adds a lot of visual noise to the code, without making it any clearer. Also, it’s not obvious whether the words are chosen from the POV of the caller or the callee, in contrast to the existing syntax, which works from both points of view. And finally, this:

let mut x = 5;
let y = &mut x; // Existing syntax.
let ref mut y = x; // Existing syntax if you want to be more wordy.
let y = share x; // Share to myself? That doesn't make much sense imo.

#4

Is there any reason we can’t have them both, with the loan/borrow and share just being aliases of &xyz and &mut xyz?

let y = share x; // y is sharing space with x so modifying one modifies them both.
let share y = x; // this would be equivalent and probably clearer
let share y = share x; // this would be the wordy more explicit version of this call, if I ever got confused on how to use it I could default to this and know for sure it's going to work as I expect

let y = &mut x;
// this is not self evident to me, it looks like it's explicitly stating that X can change independently of Y
// I know this isn't true because I've read the documentation, but for beginners it's confusing.  I know I want to share something, but &mut sounds like I just want to put a changeable version of X into Y

If &mut is coming from c++ then it makes sense to keep it for existing and transitioning users, but that doesn’t mean it’s usage is clear to newer users and those coming from something other than c++. But I thought borrowing was new to RUST not something that came from c++?

p.s. Normally I wouldn’t have said anything and just accepted the old way of doing things as an artifact carried up from assembly, but I think one of the stated goals for RUST is to make an effort to be more ergonomic when a change in syntax improves readability and doesn’t negatively impact compiling and optimization

[edit]
changed around formatting to try and be clearer and more concise


#5

Regarding POV, the pairing is so the compiler can verify you’re trying to catch the same thing that was thrown, and would mainly be used in function calls and as long as one exists it would give a warning to make sure you know what you’re trying to use rather than a hard error (unless you were trying to [borrow] from a [show] of course).


#6

Here would be some variable only examples of the loan / borrow syntax since share / share hides the POV changes

let mut num = 5;
let borrow y = num;       // these are equivelant, but this is probably the clearest most concise version
let x = loan num;         // these are equivelant, and they all implicitly inherit the mutability of what they borrow, since the whole point of borrowing is to be able to change something then give it back
let borrow z = loan num;  // these are equivelant
y++; x++; z++;
(release?) x,y,z; // I'm not sure of a good keyword to explicitly return a loaned item early (before end of scope) since Return is already a keyword

[NOTE]
I’m not sure if it’s better etiquette to reply to myself so as to not change the heart of a post, or if it’s better to consolidate subsequent replies into a single super post?


#7

I went there but it seems the accounts aren’t shared with users.rust-lang.org, is this correct?


#8

Yeah, they do have separate accounts, but you can login to both with your GitHub account if you want.


#9

[off topic]
Do you happen to know if i can tie an account I created here directly to my github, or should I abandon this one and start fresh?


#10

Not really I suppose, except now you have a lot more words as special keywords that one probably cannot use as identifiers.

As for your share example, the modification scheme makes it very difficult to reason with, cause you’re allowing modification from all reference holders, which is equivalent to an unsafe pointer use in C/C++. While Rust’s model allows compile time reasoning safely.

I am not sure if requiring the programmer to pick the right keywords gives any power to expressiveness or safety. As it stands, the compiler can already verify you’re catching the same thing as it was thrown? I am not sure if I’m missing anything here.

Even for more complex languages like Pony, the sharing rule is still same as Rust’s one(see “Sharing” section).
The “if either all read (exclusive) or one writes, then you have safe concurrent access” is a well-known principle and it’s intuitive(and probably has a proof somewhere)

that it holds universally in all scenarios, and it is best to pick the simplest and (supposedly) logically sound principle as your foundation.

Not saying more complex systems are not okay, but you’ll need to produce proofs that the system is okay to convince most people.

Overall I’d agree with @quadrupleslap that you’re adding a lot of visual noise in the design.

Slightly off topic : I feel like you might enjoy experimenting with using/creating new languages if you’re not in that field already. If that is indeed the case, you might be interested in Racket or something that allows you to write DSL easily, but I have no idea if you can implement the sharing rules in Racket that easily. I recall spotting a language creation framework in OCaml at some point, but I can’t remember the name atm.

Either way, I’m not in the field of programming language research, so apply salt etc.

I’m not sure at all whether you can tie it back or not, and I don’t think it matters too much. But I don’t think anyone would complain either if you decide to abandon this account just to keep things consistent by using GitHub on all accounts.


#11

My understanding is the current approach is to be more explicit so it’s easier to spot mistakes and be clear about your intentions. Using words rather than &mut makes what you’re trying to do more clear to non c++ programmers, and the check at each end is to help catch mistakes the programmer may have made (this is already happening to a large extent ex. with methods that strongly request you to use .expect or match)

it’s sort of the language equivalent of saying Get-DistributionGroup instead of gdisgr

I’m not at all disagreeing with the approach, it’s the words used to state your intentions in the language that’s confusing to those of use who don’t come from c++. ‘share’ and ‘borrow’ make sense to most people who aren’t programmers, &mut doesn’t

Sorry if I wasn’t being clear but I’m not trying to change the way the system works, just trying to give better conveyance to newcomers. I thought it was doing these checks already (making sure borrows are passed correctly), does the compiler not check that a borrowed variable was sent as a loan rather than a peek?


#12

Well in your example, specifically the following lines

let y = share x; // y is sharing space with x so modifying one modifies them both.

share is neither & or &mut, so I just assumed you were proposing a different system. Please correct me if I misunderstood.

That has more to do with the type structure and values than sharing though? Sorry again if I misunderstood.

Similar to above, your share is not consistent with any of Rust’s borrowing rules I think.

Also, your loan/borrow is not consistent with the sharing rules of Rust’s either.

Problem with share:

  • Exactly one party has read and write exclusive access as per the rules if you do mutable share(with &mut), so you can’t emulate that in Rust.

Problem with loan/borrow

  • If you let the other party change, that means the value is borrowed by &mut, so you cannot look at it either, so you can’t emulate that in Rust.

#13

Most likely I was just using the wrong translations of intention into RUST syntax

But that in itself is evidence that the current syntax is confusing to people who come from somewhere other than c++

Also I’ve recreated this post (with some modifications) to internals.* here: https://internals.rust-lang.org/t/a-possibly-more-erognomic-syntax-for-borrow/7898
Note: based on some of the concerns and confusion here I’ve modified the OP there so Please re-read it before posting


#14

To be honest, the &mut syntax doesn’t share much in common with C++. I wouldn’t even go as far to say anyone familiar with C++ has a much less steep learning curve with the ownership rules. C++ has more in common with Rust’s unsafe raw pointers, which are probably best avoided for the same reasons. The borrow operator only looks like the address-of operator on the surface.

More on-topic, I am going to side with a lot of the commenters from the other recent thread that asked about syntax changes; Rust and Data Science // First impressions from an outsider

In short, minor changes like this generally don’t “move the needle forward” in language design. But they would carry high risk of extra tech debt and instability (breaking changes). A lot of syntactic pieces of a language are “learn once, use often”. And even more contentious is that bikeshedding on exactly how syntax should be changed will always be subjective and highly debatable.


#15

That may be. It also may be an indicator that someone hasn’t read the documentation deeply enough, considered the issues being addressed carefully, asked questions for stuff they don’t fully understand, made some experiments to see how things work and garner some intuition about it.

I’m not much of a fan of “if I don’t immediately understand it something is wrong with the implementation/documentation/concept” as opposed to “maybe I need to work on this more and try to garner a deeper understanding”.

As a 100% self-taught software developer I have almost 0 patience for that kind of thing. I assume that when I encounter something I don’t understand, it is I who need to rise, not that the concept as created/implemented/conceptualized by others is somehow lacking.

Asking questions when you don’t understand is good. Proposing to upend how something works because you haven’t spent sufficient time to understand how something works, or even spent the time to get clear on what the concepts are doing (as opposed to what you think they are doing after a cursory review) is terribly counterproductive.

Just my humble opinion.


#16

If you follow to the new request I explicitly address this, I’m not proposing changing anything about the actual process. What I’m saying is that in this case words have better affordances than symbology, and without making any changes to what the compiler is actually doing, or leaving behind the old way of doing things, we should discuss if there might be a better approach for the we breed of programmer coming down the pipe who isn’t like us


#17

This is a good opposing stance, and someone already raised a similar point. I actually wasn’t pushing for changing HOW we’re doing these passes, I thought much of this was already happening (and I was wrong about that). What I was trying to say is that to the compiler the concept should be more important than the actual words, and so the actual words we choose to use should be clear and intuitive to our expected users, it’s why we use .add() instead of .prostheto(), they mean the same thing but one is easier for most of us to understand because we (like most western programmers) speak english.

And since borrowing is such a new concept It’s important that we consider both how to make it as clear and intuitive to new programmers (including the new flood coming from higher level languages and web programming) and the trajectory it’s going to take (meaning if &mut gets locked in now it’ll stay that way for 20+ year if Rust gains the kind of acceptance it deserves)

Please bring the discussion back over there:

And just so we’re all on the same page, when I’m saying affordances this is what I mean


#18

Unfortunately, the actual words you’re asking to replace are already being used in a very large corpus of existing source code. Changing these kinds of “minor” things for the sake of patching a learning curve issue has to strongly outweigh the downsides. That was my point about “moving the needle forward”.

If typing more keywords is provably a better alternative, then you might be onto something. Until then, it is just a minor annoyance to write the cryptic symbols into your long-term storage once and forget that you ever wished things were different. Remember, I’m not talking about compiler internals, here; Changing the human interface to a language has an incredibly high cost, and I am skeptical that any benefit from such a change would be worth the price.

For a few examples of languages that have taken rather drastic roadmap divergences with syntactical changes (and the turmoil it caused with porting old source code) just look at Python 2 to 3, and Perl 5 to 6.