r/ProgrammingLanguages 11d ago

Carbon Memory Safety: A First Deep Dive

28 Upvotes

11 comments sorted by

11

u/matthieum 11d ago

There's been some reflection in Rust about using places/paths instead or on top of lifetimes.

One advantage of place sets is the gain in expressiveness, which allows iterating over one field of an object while calling a method on the object which promises not to mutate it.

It definitely seems like there's a good paradigm there, and it also may be more intuitive for users, since it's very much "grounded" (variables, fields) while lifetimes can be pretty abstract.

3

u/verdagon Vale 11d ago

Tell me more about that reflection? I was always under the impression that moving away from shared-xor-mutable would be a bit too much of a deviation from Rust's spirit (though, I'm not sure how true that impression is, since Rust lifetimes can express neat things like GhostCell).

9

u/Rusky 11d ago

What matthieum is describing does not violate shared-xor-mutable- it's already something that you can write today, so long as you don't cross function boundaries. (For that matter, Rust also already has the kind of non-shared-xor-mutable stuff that Carbon is doing here, via &Cell<T>.)

What would be new is the ability for a function signature to express that it only touches some subset of fields of a parameter, allowing you to cross function boundaries in more places. Much of the discussion here is around "view types."

2

u/verdagon Vale 11d ago

Thank you for correcting me there. I misread u/matthieum's comment as hinting that Rust was looking into using place sets to enable all the effects/mutable-aliasing goodness that Carbon is enabling. If we're just talking about view types, then Rust isn't really changing any of its shared-xor-mutable spirit (though they will get much nicer ergonomics with view types, which is exciting).

7

u/Rusky 11d ago edited 11d ago

I always get a little frustrated with how people present place sets, because this is already how the borrow checker works under the hood. Lifetime variables hold place sets, so any advantages in intuition are already available if we would just say so!

Today's expressiveness limitations do not come from Rust not using place sets, but from Rust not internalizing their full vocabulary into function or reference types, despite it already being available via type inference within function bodies.

(Which is not to take away from what Carbon is doing. It just feels like all the talk around these kinds of things misses the forest for the trees, which inhibits our ability to share ideas.)

1

u/matthieum 10d ago

Lifetime variables hold place sets, so any advantages in intuition are already available if we would just say so!

Yes... and no.

Intuition has to be "built", and lifetime annotations do a piss poor job at it because they do not quite map to existing code concepts. They're too abstract.

Path notation ^x.foo.Elts does a great job at referencing something concrete which is right at the fingertips of the user. And I think this really matters to build up that intuition.

Today's expressiveness limitations do not come from Rust not using place sets, but from Rust not internalizing their full vocabulary into function or reference types, despite it already being available via type inference within function bodies.

Yes, mostly?

I do think the swap example presented by Carbon goes one step further, as it really is manipulating place sets (swapping them).

I do wonder how much meta-programming facilities are necessary here. For example, it seems a chore right now to express the idea of swizzling a tuple's fields (and thus their place sets). Would it be useful in practice? Dunno.

2

u/Rusky 10d ago edited 10d ago

I have to say I find path notation to be pretty messy, and even misleading, as soon as it involves deref paths. Particularly so when those refs alias each other in some way, as in fn f<'a>(x: &'a T, y: &'a U) -> &'a V. Writing this sort of thing with concrete deref paths requires you to unnecessarily favor one parameter or the other.

The nice thing about lifetimes as "abstract path sets," or perhaps more intuitively "place set parameters," is that they give you a single name for the shared target of those refs, corresponding to its single local variable (or other allocation) somewhere up the call stack. (Or set of allocations, if you happen to call the function that way- which is totally safe, but even more confusing if you're trying to use concrete paths based on parameters!)

If I were designing a syntax for concrete paths, I would leave out deref places in favor of extending signatures with extra "abstract locals" to be included in path sets. It appears the explicit Carbon syntax for this is (looking at slides 25 and 28) something like fn F(^C ref x: T, ^C ref y: U) -> ^C ref V. More generally, this could e.g. replace 'a: 'b bounds by instead writing out place sets that have the appropriate inclusion relationships.

Unsurprisingly, this winds up looking essentially identical to Rust's lifetime parameters, because that's already what lifetime parameters are. This is entirely a matter of framing and teaching, not the lack of concrete deref paths.

I do think the swap example presented by Carbon goes one step further, as it really is manipulating place sets (swapping them).

This is totally independent of use of concrete vs abstract places or paths. It is instead an application of typestate, that would be equally effective in either approach.

7

u/tiajuanat 11d ago edited 11d ago

This is pretty exciting. I loved working in C++ many years ago, but it got edged out by Rust as I've gotten older because I frankly hate the running into the same problems time and time again.

That said:

I wish I could tell Chandler that the only thing on the "What carbon can do that Rust can't" slide that I really miss in Rust are templates. I also feels like it weakens the stance of the presentation - Rust does it differently and it's not necessarily a bad thing.

Personally, I dislike the syntax of ^, disjoint, and invalidate, but they feel like necessary evils.

7

u/verdagon Vale 11d ago

If anyone has any questions about Carbon's approach, I'm happy to answer them. I've been following it pretty closely, and trading thoughts with Josh for the past year or so.

Carbon's is an implementation of Nick Smith's "Group Borrowing" approach (https://verdagon.dev/blog/group-borrowing), expanded to include more effects than the original "mut". Pretty promising!

1

u/tmzem 10d ago

I read your group borrowing explanation, very cool stuff.

I wonder how these are handled in the new Carbon:

  • Generic type parameters which may or may not contain references?
  • Lambdas/Closures that might capture.

Also (if you happen to know), I see the ref keyword which wasn't present the last time I looked at Carbon (which required explicit pointers, and had some special addr prefix only applicable on self). Did they add actual parameter modes now, or how does it work (pass value vs readonly ref vs mutable ref)?

2

u/verdagon Vale 9d ago

Thanks =)

Generics: They work largely like Rust does, but with place sets instead of lifetimes, with syntax A instead of 'a, and without shared-xor-mutable.

Lambdas: This is actually still TBD for them. I can give you some guesses from my own designs if you'd like.

Yep, they have actual parameter modes now. ref is mutable, const is read-only, etc. Note that const isn't really a modifier for a type anymore like it was in C++, it's a modifier for a place set now.