Beyond the &

(lwn.net)

89 points | by PaulHoule 2 days ago ago

13 comments

  • amluto an hour ago

    This is neat, but I admit I find the MutexGuard example to be uncompelling. The article says:

    > For example, how to turn (the handle for) a MutexGuard<RenderState> into a MutexGuard<String> when the programmer writes state.template.

    Please don’t. In the examples later in the article, maybe an Option<&A> can be dereferenced and projected to get an optional reference to something inside the A. Fine — Option is logically a value, and you either have a reference or don’t have a reference.

    But a MutexGuard is not just a value. It’s a representation of a lock held on a particular mutex. Dropping it has side effects! If I have a MutexGuard<RenderState>, this means that there is a Mutex<RenderState> and that, when the guard drops, the mutex unlocked. A field in the RenderState should not have a guard unless it has its own mutex!

    ISTM what’s actually wanted for mutex guard field projections is one of two things:

    1. Nicer syntax. The article points out that this works today:

        let state: &mut RenderState = &mut *state.lock().unwrap();
    
    Great! Improve the syntax! Or:

    2. Use the new machinery to make the actual motivating example work without making up a MutexGuard where none should exist. Have the field projection return an ordinary reference. Is this harder for some reason that having it return MutexGuard?

    • masklinn 34 minutes ago

      I thin the part that you missed is that a MutexGuard is an owned value. That’s the gain of having a mutex guard projection, the projection is an owned type which keeps the lock, well, locked. Having a field projection return a reference is no use because… that already works fine today, it’s deref. But that does not work if you need an owned reference. Same for field projecting everything else e.g. an arc.

      > A field in the RenderState should not have a guard unless it has its own mutex!

      I don’t agree with that. The original mutex gives access to the field via its guard, nothing wrong with keeping the guard and the field and getting rid of the stricture around it (for the purpose of access). It’s not like mutex guards have a static identity relationship to their mutex.

      • amluto 3 minutes ago

        Hmm. The example in the article involves overlapping references to .cache and .template. I’m not seeing how owned guard protections would make sense.

        I can maybe see how one might have a MutexGuard for the entire RenderState and then pass an owned MutexGuard<PartOfTheState> somewhere else, but this seems like it should not be able to coexist with other guards holding the same mutex. (How would it work? Reference counts on parts of the mutex?)

  • weinzierl 8 hours ago

    "A place is Rust's equivalent of C's left-hand sides (lvalues): a location that values can be read from or written to. The difference between a place and a pointer is that a place is an abstract expression that exists at compile time, such as state.cache, and a pointer is one way to represent a place at run time."

    That was a helpful bit.

  • chombier 13 hours ago
  • creata 14 hours ago

    > That works, but it's not particularly intuitive.

    It looks fine? The code fails because it dereferences the MutexGuard twice, so you instead dereference it once. It's hard to imagine that this contributes much to "Rust's difficult learning curve".

    Rather, introducing even more potential implicitness on every `.` seems like it would make the language harder to learn.

    Is there an example that really benefits from these features?

    • masklinn 12 hours ago

      > Rather, introducing even more potential implicitness on every `.` seems like it would make the language harder to learn.

      The problem is that built-in pointers (references and box) are already special cased in the compiler to Just Work (tm), so this creates confusion when switching between those builtins and third-party pointers, or even stdlib pointers which aren’t special cased by the compiler.

      So this is less about “potential implicitness on every .” and more about making places more first-class, and non-builtin smart pointers (both first and third class) being better able to express their semantics to the compiler, to lift every pointer to the ergonomic capabilities of builtins.

    • ivanbakel 9 hours ago

      It's missing the point of these changes to suggest that the current version is good because the error has a just-so explanation. The same code without the MutexGuard is conceptually the same, and there a mutable reference is dereferenced twice without an error. Should the programmer need to know that they themselves need to manually write the mutable-reference coercion that the compiler would normally do to unlock the same expressivity? Is this good and intuitive language design?

      >Is there an example that really benefits from these features?

      That question would be easier to answer if you would spell out what you don't find compelling about the article's example, beyond the fact that you can identify a reason for the error to occur.

      • creata 6 hours ago

        > what you don't find compelling about the article's example, beyond the fact that you can identify a reason for the error to occur.

        It's not just that there's a reason for the error to occur, it's that (1) the line between what the borrow checker can and can't do is currently clear, even if it's privileging references and boxes, and (2) the fix is a one-line change. Exposing more language hook points via traits to save one line here and there is what doesn't seem compelling to me, which is why I asked for an example where it leads to a more significant improvement in the code.

        Like, the mentioned roadmap page says "But Option<&mut T> or custom smart pointers require awkward .as_deref_mut() or .reborrow() calls"; what I'm asking is: how awkward?

  • NooneAtAll3 7 hours ago

    I don't understand, how is this connected to Linux?

    Why is it on linux news blog?

    • masklinn 7 hours ago

      I don’t know, maybe because rust is of interest to the wider Linux community and has been pretty much since its inception (https://lwn.net/Articles/547145/)?

      And with the inclusion of rust in the kernel, and thus the kernel’s need to express its own abstractions and requirements (as well as the impossibility to use alloc and thus box) the limitations (ergonomic and otherwise) of “userland” smart pointers are more salient and relevant than ever.

    • azornathogron 6 hours ago

      LWN.net doesn't exclusively cover Linux kernel issues. From the FAQ,

      What is this LWN thing?

      LWN.net aims to be the premier news and information source for the free software community. We provide comprehensive coverage of development, legal, commercial, and security issues.

      https://lwn.net/op/FAQ.lwn

    • xacky 7 hours ago

      We have Rust in the kernel now, of course the language that Linux uses is relevant.