Skip to content

Clay for C++ programmers

jckarter edited this page Nov 20, 2010 · 33 revisions

Clay vs C++: Executive summary

Clay's primary goal is to be a systems programming language, with a focus on performance and generic programming. Clay thus shares many features with common C++ implementations:

  • Value semantics, and Resource-Acquisition-is-Initialization (RAII)
  • Raw memory access, including pointers and pointer arithmetic
  • Minimal runtime dependencies—no required VM or garbage collector
  • ABI compatibility with C
  • Templates (called generics in Clay)
  • Raw machine access through inline LLVM and inline machine assembly language
  • ...

However, Clay also discards many C++ features:

  • Source compatibility with C
  • Language-level support for object-oriented programming (although Clay is flexible enough in which to implement a custom object system and to provide bindings to foreign object systems such as Cocoa, glib, or COM)
  • ...

On the other hand, it adds many new features over C++98:

  • Pervasive type propagation—nearly all types can be inferred
  • Generic specialization based on type predicates
  • Multiple dispatch through variant types
  • ...

and has many features also added by C++0x:

  • Variadic generics and types
  • Lambdas
  • Perfect forwarding
  • ...

Comparing "Hello World"

In C++:

  #include <iostream>
  int main() { std::cout << "Hello world!\n"; }

In Clay:

  main() { println("Hello world!"); }

This simple example introduces some basic differences between C++ and Clay:

  • Clay's io library is part of its prelude, a module implicitly available to every Clay module, so there is no need to include a header file to access the println function.
  • main does not need to declare its return type, because Clay will infer the type from the body of the function. Since there is no return statement, Clay infers that it returns no values.
  • Clay's println is a variadic function that prints zero or arguments to stdout, followed by a newline. C++

Comparing object lifecycle

In C++:

  #include <cstring>
  using std::strdup;
  using std::free;

  class silly_string {
  private:
      char *str;
  public:
      silly_string(char *s) : str(strdup(s)) {}

      silly_string(silly_string const &s)
          : str(strdup(s.str)) {}

      ~silly_string() { free(str); }
      
      silly_string &operator=(silly_string const &s) {
          free(str);
          str = strdup(s.str);
      }
  };

In Clay:

  import libc.(strdup, free);
  
  record SillyString (str: Pointer[CChar]);
  
  overload SillyString(s: Pointer[CChar]) returned: SillyString {
      returned.str <-- strdup(s);
  }
  overload SillyString(s: SillyString) = SillyString(strdup(s.str));
  
  overload destroy(s: SillyString) { free(s.str); }
  
  overload assign(to: SillyString, from: SillyString) {
      free(to.str);
      to.str = strdup(from.str);
  }

(commentary)

Clone this wiki locally