Hacker Newsnew | past | comments | ask | show | jobs | submitlogin

How does ATS provide safety guarantees without additional semantic information about the C code it’s linking to? If it does require that, then you’re in the same boat as Rust: your choices are trivial interoperation with no safety at the interfaces or encoding the semantics of the C interface in the new language. The only difference I can think of is built in ability to inline C from headers (based on your comment about kmalloc), is that the case for ATS?


It's not only inclusion of the headers. ATS compiles to C, and it can inline C without FFI. The benefit that you receive is the ability to introduce gradual automated proofs around unchanged stable interfaces that are known to be safe. You start with inlining everything but the small core that is proven to be safe. Then you can iterate indefinitely at the desired pace to bring new layers of formally verified API in places of previously unverified C calls without breaking backward compatibility and expected runtime profiles (safe pointer-based memory access [1] and stack-allocated closures [2] as an example)

[1] http://www.ats-lang.org/MYDATA/SPPSV-padl05.pdf

[2] http://ats-lang.github.io/DOCUMENT/ATS2TUTORIAL/HTML/c1267.h...


This may need fewer code changes (if the code in question is correct and the interfaces can possible be implemented in a safe way, that is) but it still requires developers to write proofs in a dependent type theory. I’ve only looked at ATS briefly, but I’m familiar with a lot of the other languages in the space (Idris, Fstar, etc.). Are ATS’s proof tactics drastically more productive/brief than those (F* in particular, which has great aliasing reasoning)? If not I think you’re radically overestimating the approachability of this route compared to Rust.


I’ve used ATS, Idris and Rust, and I find ATS way more cumbersome to use for writing proofs than idris.

I’ve never used ATS to do what the GP is suggesting of incrementally wrapping C with proofs, but I can’t imagine this being simpler than just rewriting the C in Rust. You wouldn’t get the same proofs, but you would get memory and thread safety, which for many apps would be an incremental improvement.

I haven’t taught ATS, but I found it harder to learn than Idris, and I can’t imagine C programmers which have a hard time with Rust learning it quicker than they would learn Idris or Rust.


> but I can’t imagine this being simpler than just rewriting the C in Rust.

sometimes it's impossible without falling back to unsafe Rust, which kind of undermines the initial incentive. C codebases heavily utilise pointer arithmetic programming.

Simplicity is a good trait though, and there are a few promising initiatives in that regard in ATS3 - https://github.com/githwxi/ATS-Xanadu#project-description


"Sometimes" and "kind of" are doing a lot of work there.

In reality most Rust code hardly ever needs to be unsafe and the existence of unsafe code in libraries you use hardly ever has any impact on the security and stability of what you ship, because there just isn't very much of it compared to the safe code. The "trophy cases" for Rust fuzzing bear witness to this.

> C codebases heavily utilise pointer arithmetic programming.

You don't write unsafe Rust code everywhere C code would use pointer arithmetic. You use safe Rust idioms and APIs instead.

If it turns out that writing Linux drivers in Rust requires writing a lot of unsafe Rust code in each driver, then that would certainly be a failure. I don't see any reason to believe that will be the case.


The point of Rust is not to eliminate unsafe, but to minimize it and make the actual unsafe operations visible and easily auditable.


ok, I'm fine with that, the question is why it is technically better for Linux Kernel than a proper formally-verified implementation that doesn't require Rust toolchain?


Because I can bet that it’s easier to write correct Rust than to write correct ATS.


My experience may be too specific to reason about productiveness with ATS when it comes to other developers. I knew some Haskell and I've already read the Idris book when I discovered ATS, and I knew some C from university courses. The proofs require the same mental model, totality check principles are the same, resource utilisation principles are the same, so for me the difference when comparing these two langauges is mostly about the things available apart from the common features: ATS prelude has many predefined constraint functions that facilitate low-level programming with pointers over unboxed data.

I haven't tried F*, but from quick googling of "aliasing", it seems that a similar kind of checks can be achieved with view-changes in ATS (but please correct me if I'm missing the point) - http://ats-lang.sourceforge.net/DOCUMENT/INT2PROGINATS/HTML/...


> it can inline C without FFI

I'm not sure what you mean by "FFI" (I would apply the term "FFI" to the way ATS interoperates with C, too), but you can certainly get cross-language LTO between C and Rust, since they both compile to LLVM IR.

> you can iterate indefinitely at the desired pace to bring new layers of formally verified API in places of previously unverified C calls without breaking backward compatibility and expected runtime profiles

You can do this in Rust, too.


I meant not having to think about wrapping/boxing/unboxing, as they have exactly the same native data representation, so it's slightly different than LTO. But mine using "FFI" there was probably too vague and frivolous, I agree.

> You can do this in Rust, too.

not always within boundaries of safe Rust though (assuming each iteration is allowed to brings zero unsafe/unverified code), which undermines the initial safety argument.


> wrapping/boxing/unboxing, as they have exactly the same native data representation

Rust's Box<T> and Option<Box<T>> have the same representation as a C pointer to the same type, so this is true of Rust too.

You do have to wrap the type in source code to indicate what the ownership guarantees/contracts are. (Is this not also true of ATS? That is, if ATS code calls a C function that returns a pointer, how do you know how long that pointer is valid for and whether you're expected to free it?) But there is no runtime overhead/conversion.

> which undermines the initial safety argument

No, adding unsafe Rust does not undermine the safety argument (at least, no more than having any C somewhere in your address space does). See my reply in the comment section of TFA: https://lwn.net/Articles/830158/

(It's unfortunate that the Rust language used the "unsafe" keyword to mark blocks where you can access raw pointers, because it causes people to think that using such code is unsafe. Something like "manually_verified_safe" would have been better - the whole point of putting an "unsafe" block on a safe function is to write code that is safe to use that cannot benefit from the Rust compiler's checks. But at the boundary with C, it is impossible to have automated checks, because the relevant typing/safety information doesn't exist in C in the first place. It's no different from ATS code using $UN where you've manually verified that you're using a C function or C-created data in the way it's intended to be used.)


> No, adding unsafe Rust does not undermine the safety argument

But it does, see my answer in the thread below regarding ring buffers - https://news.ycombinator.com/item?id=24337184

> Is this not also true of ATS? That is, if ATS code calls a C function that returns a pointer, how do you know how long that pointer is valid for and whether you're expected to free it?

the C implementation needs to be known, then the signature (interface) of the extern-annotated C function would define a pointer that will be associated with a proof object. Every ATS program that compiles must consume all instantiated proofs (with a proper proof-consuming function), and the proof itself can indicate whether a pointer is stored/taken care of inside the C function or it should be taken care of outside the function (the latter is indicated by a prefixed "!" to the pointer type). An example could be found here - https://bluishcoder.co.nz/2012/08/30/safer-handling-of-c-mem...


> ring buffers in Rust use "unsafe" and at some point there was a logical CVE in vec_deque that broke one invariant of the unsafe block

First, you're talking about two different things here. Originally you were talking about incrementally adding Rust bindings to C code and how that obligated you to write "unsafe". Now you are talking about pure-Rust implementations of data structures that use "unsafe" for optimization purposes. The second one is much more dangerous.

Second, it does not undermine the safety argument - quite the opposite, as demonstrated by the fact that code that used VecDeque remained unchanged after the bug fix. Everything that the Rust compiler proved about code that used VecDeque, on the assumption that VecDeque was sound, remained just as proven after the bug was fixed and VecDeque was changed to be actually sound. There was no ex-falso-quodlibet problem. There was a clear delineation in the safety argument: on one side, a human was obligated to check that VecDeque was implemented soundly, and on the other side, the Rust compiler checked that every user of VecDeque did so soundly. The first part was done incorrectly and then fixed; the second part remained done correctly.

> the proof itself can indicate whether a pointer is stored/taken care of inside the C function or it should be taken care of outside the function (the latter is indicated by a prefixed "!" to the pointer type).

Then this is an axiom as input to the proof, not a proof itself. That is, ATS is no more capable than Rust of magically determining what the unstated invariants of C code are; it can only prove that certain things logically follow from certain assumptions. This is precisely what Rust does, too, except it is clearer than ATS because any use of such unchecked assumptions are clearly marked with the "unsafe" keyword.

In the linked ATS code, I see no indication, as a human reviewer, about which parts I should carefully audit (e.g., the annotation of the "extern fun") and which parts ATS has checked for me (e.g., the implementation of "string_to_base64"). In Rust, you can very quickly search a codebase for "unsafe" and see what needs auditing. (And in fact this technique empirically works well for finding unsoundness in production Rust code, and I can point to multiple examples of it. I wonder what people do when reviewing production ATS code.)

Again, I'm not saying ATS isn't cool. I'm not saying it wouldn't be great to have ATS support in the Linux kernel. Please work on this. There are, in fact, lots of things that ATS can do that Rust cannot do, and they are helpful to have. But I think the specific things you are claiming are things that Rust can do just fine or that ATS in fact cannot do.


> Then this is an axiom as input to the proof, not a proof itself. That is, ATS is no more capable than Rust of magically determining what the unstated invariants of C code are

I've never stated that there's such a capability. I was saying that unlike Rust, ATS can be used to gradually rewrite every C call into a safe and formally-verified equivalent function that has exactly the same performance and memory charactersitics as the original C function.




Guidelines | FAQ | Lists | API | Security | Legal | Apply to YC | Contact

Search: