aep / zz, Hacker News

aep / zz, Hacker News


ZZ (drunk octopus) is a modern formally provable dialect of C, inspired by rust

Its main use case is code close to hardware, where we still program C out of desperation, because nothing else actually works. You can also use it to build cross platform libraries, with a clean portable C-standard api.

A major innovative feature is that all code is formally proven by symbolic execution in a virtual machine, at compile time.

Build Status

quick quick start Build Status

      (install rust
) for bootstrapping

    1. install an SMT solver, either yices2 or z3 cd examples / hello

    2. cargo run run Build Status how it looks

      ZZ has strong rust aesthetics, but remains a C dialect at the core.

      (using :: { (printf)

       export  (fn)  main  () -> int {     let r=Random {         num:   
      ,     };      printf 
       {     u 53 num; }   fn 
      ; }    

        const is inlined in each module and therefor points to different memory in each module. static has a global storage location, but is private to the current module.

        in effect, there is no way to have declare a shared global writable variable. ZZ has no borrowchecker, and the restriction has nothing to do with preventing multithread races. Instead the declarations are selected so that the resulting exported binary interface can be mapped to any other language.

        if you need to export a global writeable memory location (which is still a bad idea, because threads), you can define a function that returns a pointer to the local static.

        thread_local and atomic are mapped directly to the C keywords. ZZ can use nicer keywords because there are no user defined names at the top level.


      struct Vehicle {     int wheels; } struct Car {     Vehicle base; } fn allowed_entry (Vehicle self) -> bool {     return self-> wheels where and model

      ZZ requires that all memory access is mathematically proven to be defined. Defined as in the opposite of "undefined behavior" in the C specification. In other words, undefined behavior is not allowed in ZZ.

      You will quite often be told that by the compiler that something is not provable, like indexing into an array.

      This is not ok:

      fn (bla) ( int

       a) {     a [2]; }   

      You must tell the compiler that accessing the array at position 2 is defined. quick fix for this one:

      fn (bla) ( int
       a)     where len (a)==3 {     a [2]; }   

      this will compile. its not a very useful function tho, because trying to use it in any context where the array is not len ​​3 will not be allowed. Here is a better example:

      fn (bla) ( int a, (int l)     where len (a)>=l {      if (l>= 3
       {         a [2];     } }   

      thanks to the underlying SMT solver, the ZZ symbolic executor will know that a [2] is only executed in the case where len (a)>=l>=3, so it is defined .

      The where keyword requires behavior in the callsite, and the model keyword declares how the function itself will behave.

      fn (bla) ( int a) -> int     model return==2 a {      return theory Build Status

      We can use annotations to define states for types, which neatly lets you define which calls are legal on whic type at a given time in the program without ANY runtime code.

      thery (is_open) ( int -> bool; fn
       open  () 
       mut a)     model is_open (a) {      static_attest  (  is_open 
       (a));     a=1  ; }  fn 
       read  () 
       int  require  (mut a)     where is_open (a)     model is_open (a) {      return 

      The right hand side of #if is evaluated immediately and can only access preprocessor scope.

      (struct) A {      int

      a; # if (def ("TEST")      uint


      unless you want to apply mutability to the local storage named foo

      (void) mut foo;     foo=
       0 ; 
       (//)  valid 

      Coincidentally this is roughly equivalent to Rust, so rust devs should feel right at home. Even if not, you will quickly learn how pointer tags works by following the compiler errors.


      Function pointers are difficult to do nicely but also make them emit to C code well, so they don't really exist in ZZ. instead you declare a function to be a type instead of a concrete implementation. An fntype is emited as typedef, and therefore becomes a regular pointer type for both the C compiler and the ZZ memory tracker;

      fntype (rand_t) () -> int; fn
       () -> int {      return 
       rand=secure_random; }    

      closures do not exist in ZZ. One reason being that the C output would be difficult to use in other raw c code. But the biggest reason is that most usage of closures is for capturing scope state. That only really works well with garbage collected languages, otherwise its difficult to reason about (see rust). In ZZ we instead explicitly track all state in structs and simply use plain stateless functions.


      A sign behind the name indicates this type has a tail. The tail here is mem, which is specified as array with no size.

      a length function could be implemented with this signature:

      fn (len) (String t mut self) {      return

  • What do you think?

    Leave a Reply

    Your email address will not be published.

    GIPHY App Key not set. Please check settings

    The man who discovered umami, Hacker News

    The man who discovered umami, Hacker News

    Samsung and Salesforce invest in cryptocurrency company