Hacker News new | ask | show | jobs
by duneroadrunner 2628 days ago
> Cone actually does support Rust-like, lifetime-constrained borrowed references

Ah, so kind of a super-set of Rust functionality. Presumably these would require a "borrow checker" or equivalent? Is that already implemented? So how do you address the safety of, say, taking a reference to an element in a (resizable) vector? Rust's "exclusivity of mutable references" restriction intrinsically makes the vector immutable while the borrowed reference exists, but do I understand that Cone doesn't have that restriction? The "C++ lifetime profile checker", on the other hand, makes the vector non-resizable (but leaves the data mutable).

> A key requirement I have placed on references (vs. pointers) is that you can always de-reference them and get a valid value

SaferCPlusPlus provides both a pointer that throws an exception if you attempt an invalid memory access (though it could just as easily return an optional<>, and you can always query if the target is valid), and one that terminates the program if its target is ever deallocated prematurely (and thus (technically) satisfies your criteria). (The latter has less/minimal overhead.)

> A Cone programmer would need to use raw pointers to throw off the shackles of lifetime constraints

Or reference counting pointers or GC, right? The features needed to implement the pointers I mentioned is either support for calling a destructor on move operations or the ability to make an object non-movable, and, support for copy constructors or the ability to make an object uncopyable. Does/could your language support some combination of those features?

I explain the reason the pointers are important in an article called "Implications of the Core Guidelines lifetime checker restrictions" [1]. Specifically, I give an example of reasonable C++ code [2] that historically had no corresponding efficient implementation in Safe Rust. (I think it's still the case, but I haven't fully investigated the implications of Rust's new "pinning" feature.) It can, however, be implemented in a memory safe way using the SaferCPlusPlus pointers in question [3]. Basically the example just temporarily inserts a reference to a (stack allocated) local variable into a list (or whatever dynamic container), given that the local variable does not outlive the container.

> especially given that borrowed references and raw pointers are both able to point inside an allocated object

SaferCPlusPlus has the equivalent of "borrowed references"[4] (though even more restricted until C++'s "borrow checker" (the aforementioned "lifetime profile checker") is completed), and they can safely point "inside" (allocated) objects. Note that the second safe pointer type (the one that potentially terminates the program), is a "strong" pointer, and there is a simple mechanism for obtaining a "borrowed reference" from a strong pointer [5]. And from there, a simple mechanism for obtaining a reference to an (interior?) member [6].

The other pointer (the one that potentially throws an exception) would be considered a "weak" pointer, and you cannot obtain a "borrowed reference" directly from a weak pointer. But often, the weak pointer is used to target an object that can yield a borrowed reference (like a strong pointer, for example), or a borrowed reference directly.

> all the best with getting others to learn about and adopt your language.

You too :) I can see the appeal of this sort of clean, flexible language. But in the case of SaferCPlusPlus the goal is not necessarily (just) for programmers to adopt it. In part it's maybe a demonstration (to language designers such as yourself :) of a set of language elements that use run-time safety enforcement mechanisms but are a little more flexible than (and might be a good / (unintuitively) needed complement to) their counterparts that rely on strictly compile-time safety enforcement.

Oh and if you do get around to checking out SaferCPlusPlus in more depth, apologies for the inadequate documentation in advance. Feel free to post any questions you might have. :)

[1] https://github.com/duneroadrunner/misc/blob/master/201/8/Jul...

[2] https://github.com/duneroadrunner/misc/blob/master/201/8/Jul...

[3] https://github.com/duneroadrunner/misc/blob/54204445bc099987...

[4] https://github.com/duneroadrunner/SaferCPlusPlus#txscopeitem...

[5] https://github.com/duneroadrunner/SaferCPlusPlus#make_xscope...

[6] https://github.com/duneroadrunner/SaferCPlusPlus#make_pointe...

1 comments

Yes, borrowed references being lifetime-constrained means that I have a "borrow checker" that ensures that. It is only partially implemented.

You are correct that Cone supports a static, shared, mutability permission, including on borrowed references into resizable arrays. The short safety answer is array resizing is only possible when you have a unique reference to the array, so you can't run into the trouble you describe. I wrote a post about it.[1]

You left out an important clause I specified in my criteria: "with no chance of exception". Terminating the program in the event of dereferencing a reference does not meet the safety requirements I set for Cone references.

Yes, only borrowed reference have lifetime constraints. I did not mention the allocator-based reference in that quote because of context. Cone does support a distinction between move vs. copy types. Unlike with Rust, the distinction is typically inferred from the definition of the type. Currently, all memory is "pinned", but that may become more flexible in the future.

The safety strategy for Cone involves versatility: giving the programmer a curated collection of permissions and memory allocators, each with distinct advantages and disadvantages. The safety of certain options can be completely determined statically, making them inflexible but fast. Others will use a mix of static and runtime mechanisms, which offer greater flexibility but incur a runtime cost.

That said, I admit I am somewhat uncomfortable temporarily injecting a borrowed reference into a longer-lived container as snippet 4 shows. I feel like any logic able to ensure this is only done safely would be too complicated for my taste, at least for now. I understand how your mechanism would address this scenario, but again that does not ascribe to my more restrictive notion of safety. If the program does it wrong, it crashes.

Thanks for the added insight into your design choices and your kind comments. I will certainly look through your links. [1] http://pling.jondgoodwin.com/post/interior-references-and-sh...

> I wrote a post about it.[1]

I just read it, and I thought it was great. I had a similar, if perhaps not-as-well-thought-out, reaction to Manish's (I agree, excellent) post. I think SaferCPlusPlus basically implements the permission mechanisms you listed in the summary (as well as the preceding "Race-Safe Strategies" post). (Although with some of the restrictions enforced at run-time rather than compile-time.) Looking forward to Cone 1.0. :)

p.s.: btw, the link on your post to the preceding "Race-Safe Strategies" post is broken

Did you also read the follow-on post on transitional permissions?
No, I just read it. SaferCPlusPlus does support all the permission "modes" listed, except the "opaque" one, and transitioning between them (though being stuck with C++'s move semantics). The permissions modes that aren't intrinsic to C++ are enforced at run-time. Objects to be mutably (non-atomically) shared need to be put in an "access control" wrapper, which is kind of like a generalized version of Rust's RefCell<> wrapper.

As I noted in my original comment, in the "mutex"/"RwLock" case, SaferCPlusPlus allows you to simultaneously hold read-locks and write-locks in the same thread. Which seems natural, since SaferCPlusPlus (and Cone) allows const and non-const pointer/references to coexist in the same thread. But in this case it actually provides increased functionality. It is the functional equivalent of replacing your mutex (and Rust's RwLock) with an "upgradable mutex", which facilitates better resource utilization in some cases, right? It also provides new opportunities to create deadlocks, so the mutex has to detect those.

Btw, I am certainly a pot talking to a kettle here, but your "mutex1" urgently needs a better name, right?

Absolutely agree.