Is it a good idea if the keyword mut is symbolized as ~

Is it a good idea if the keyword mut is symbolized as ~
let mut a = 3;
let ~a = 3;

fn foo(a: &mut Vec);
fn foo(a: &~ Vec);

No, Rust already uses a lot of symbols for syntax, adding more won't help. For example, this is a valid expression |()|(). It's good to have keywords because it makes the code significantly easier to read.

10 Likes

You could write a macro to do this. There's no way this will change in the core language, though.

Rust used to have many more symbols, in the distant past. IIRC ~T was how one used to spell Box<T>, for example.

But Rust has consciously moved to have fewer sigils, so generally the answer to your question is no -- without substantial motivatation (like existed for ?), the default assumption is that replacing letters with a symbol is not good.

EDIT: You can also see this in the await conversation -- there were many suggestions to use a sigil for that, but the language team decided early on that the syntax would use those letters and not a new sigil well before the final form was decided.

9 Likes

No. I fail to see how replacing a keyword with a cryptic symbol helps anything much. Personally the less my code looks like a secure password or line noise resulting from communication errors the easier it is for me to read it. I rate readability as a good thing and more highly than conciseness.

5 Likes

:+1: :100:

1 Like

I think not at all. It's less clear – Rust already has many symbols, and changing a keyword to a symbol would be detrimental to readability.

let mut and &mut are actually two different features of the language, with different guarantees. let x = 1; let mut x = x is legal, but let x = &1; let x: &mut _ = x; is not.

If anything was to be changed here, I'd suggest giving different keywords/syntaxes to these two features. But Rust is past 1.0, so we're stuck with the old syntax forever.

6 Likes
  • &mutx → mutually ecxclusive
  • &shrd → shared
  • (&lazy → lazily initialized[1])

would've been my preferred reference identifiers. "mutable" and "immutable" are kinda misleading when dealing with UnsafeCell.

[1]: I hope we'll get a safe alternative to MaybeUninit for simpler cases, eventually

What is &lazy supposed to be?

fn set_u8(n: &lazy u8) {
    // Compile error, n is write-only:
    // println!("{}", n);
    
    // Compile error, n must be initialized:
    // return;

    // <&lazy T>::set is compiler "magic"
    let n: &mut u8 = n.set(123);

    // works:
    println!("{}", n);

    return;
}

let a: u8;

// Compile error, a is uninitialized:
// println!("{}", a);

// one way to initialize a:
// a = 123

set_u8(&lazy a);
// works:
println!("{}", a);

I think this gets into territory that is incredibly difficult for the compiler to prove is safe. You're basically saying you should be able to take a borrow on an uninitialized value, pass it down some arbitrary call chain, and have the compiler prove that it's initialized somewhere before its first use.

EDIT: NVM, this is probably doable. But you should still just use something like like n: &mut Option<T>.

Why is this difficult? Isn't this the same thing as out-references in C#?

1 Like

Hm... maybe I over-estimated the difficulty.

My thought was that panics, and potentially catching those panics, could make it difficult, but I suppose that would result in a break in control flow, so there would be no issue. I just discovered that you can't exclusively borrow a value in a catch_unwind block at all!

Oh, you sure can:

use std::panic::AssertUnwindSafe;
use std::panic::catch_unwind;

fn main() {
    let mut x = 42;
    let x_ref = AssertUnwindSafe(&mut x);
    catch_unwind(move || {
        *x_ref.0 += 1;
    });
}
1 Like

I worry about my future in Rust sometimes.

When I follow the link above I find:

Wrapper expressing the semantics of &out T references

In other words, this has the semantics of &'out mut MaybeUninit<T> but for the ability to write garbage ( MaybeUninit::uninit() ) into it (else coercing &mut T to &out T = Out<T> would be unsound).

This means that the reference may point to uninitialized memory (or not), and thus that writes to the pointee will not call the .drop() destructor.

I cannot begin to understand what that means or even why I would need one.

1 Like

An important trick here is that out doesn't actually exist in CIL -- to the runtime it's just a ref. So there isn't actually a reliable validation that it was initialized, but that's ok because in C# (and other .net languages) everything has a default (and must, for things like arrays). So out is actually just set-to-Default::default()-and-pass-&mut, or set-to-None-and-pass-&mut if you prefer to think of null that way. Which of course also works fine in Rust.

And I think that if we did add something like C#'s checks, it would be awkward in that it would probably need to be set even in Err paths. In a way we have "out parameters" once we get to MIR -- the _0 is passed as the location for the return value, which is expected to get initialized. So perhaps -> Option<T> is really just Rust's preferred encoding of C#'s bool(out T).

Ah, I see. I thought C# had a hard requirement that you initialize the variable before leaving the function.