logo

Carbon Programming Language

Posted on: 2022-09-27

Conclusion

Interesting, but not really mature enough yet to take too seriously.

Good

Maybe Good?

Bad

Ugly

Not sure

Discussion

From design doc

Integer literals have their type derived from their value. They can be implicitly converted, to any type that can represent the value.

Err - how do I say I want an integer value of some size with all bits set? Is the implication you have to cast, and do the required op?

Can allow forward declarations. Argues for that from the point of view of being able separate an API from an implementation.

Binding

The bindings in the parameter list default to let bindings, and so the parameter names are treated as r-values. This is appropriate for input parameters. This binding will be implemented using a pointer, unless it is legal to copy and copying is cheaper.

If the var keyword is added before the binding, then the arguments will be copied (or moved from a temporary) to new storage, and so can be mutated in the function body. The copy ensures that any mutations will not be visible to the caller.

Use a pointer parameter type to represent an input/output parameter, allowing a function to modify a variable of the caller's. This makes the possibility of those modifications visible: by taking the address using & in the caller, and dereferencing using * in the callee.

Outputs of a function should prefer to be returned. Multiple values may be returned using a tuple or struct type.

Assignments are statements, and do not return a value.

Returned var

fn MakeCircle(radius: i32) -> Circle {
  returned var c: Circle;
  c.radius = radius;
  // `return c` would be invalid because `returned` is in use.
  return var;
}

Makes clear that a certain variable is going to be returned. Somewhat like the nim idea where the return type is a variable named 'result'.

I might perhaps argue the nim way is better perhaps? The slightly odd aspect is when you do a return or a function ends, the thing returned doesn't need to be specified. Nim result

In nim var is used to indicate an out parameter.

match

Uses a somewhat unusual =>.

class

Indicates a nominal type. Meaning two types with identical structure are distinct.

Can use structural mechanism to assign multiple fields, through an implicit conversion

var sprocket: Widget = {.x = 3, .y = 4, .payload = "Sproing"};

What happens when not all fields are assigned?

Methods define the 'this' in [] before the method

fn Offset[addr me: Self*](dx: i32, dy: i32);

Uses impl, virtual, abstract on methods to indicate override, virtual and =0 in C++.

Unformed state

Seems the idea is to describe what happens when moving. The ~ operator indicates move. That appears to be a 'surprising' choice. Using a character does make writing the syntax short.

Package handling

Probably worth digging into a bit more how this work. Some interesting properties

As an example

// Package name is `Geometry`.
// Library name is "Shapes".
// This file is an `api` file, not an `impl` file.
package Geometry library "Shapes" api;

From Calling Convention

From Calling convention discussion

Parameters to a function, cannot have their address taken unless they are marked var.

Theres this subtle observation about this. If we use

void doThing1(const T& v) {}
void doThing2(T v) {}

We can think of doThing1 as being a fast version of doThing2, because it doesn't make a copy. But perhaps(?) there is an issue on ABI boundary, where const T& has to be handled as an address.

Leaving aside if this is true, it's nice to not have to litter everything with const& and be confident the compiler will do something reasonable.

What is not clear from the ABI point of view - is what are the rules such that we know something is passed by register or by pointer?

There is also this point about doThing2 - saying in carbon it will not do a copy.

What does var on a parameter mean? Does this mean it's a new variable, that's why can take it's address?

Namespaces

Can define a namespace and nested namespaces in package. Doesn't {} as in C++, the items have to have the namespace specified when they are defined.

Alias

Allows defining a name equivalent to another name. The item doesn't have to be a value, and this means that this mechanism also works for namespaces.

Generics

Uses the [] style

fn Min[T:! Ordered](x: T, y: T) -> T {
  // Can compare `x` and `y` since they have
  // type `T` known to implement `Ordered`.
  return if x <= y then x else y;
}

Supports both 'checked' and 'templated' generics. The templated style work the same way as C++, and this probably for compatibility with C++.

Doesn't support SFINAE, but uses an 'if' clause after the decl.

It is perhaps on note that the lanugage is using [] syntax for both generic params as well as specifying 'this'. Perhaps :! is what indicates the item is not a runtime value...

A generic binding uses :! instead of a colon (:) and can only match constant or symbolic values, not run-time values.

Allows both checked and unchecked parameters. unchecked are equivalent to C++ templates. Unchecked use the template keyword.

Associated types can be used and are just listed via a letFod eaxmple

interface StackInterface {
  let ElementType:! Movable;
  fn Push[addr me: Self*](value: ElementType);
  fn Pop[addr me: Self*]() -> ElementType;
  fn IsEmpty[addr me: Self*]() -> bool;
}

Destructuring

Can destructure tuples.

// `Bar()` returns a tuple consisting of an
// `i32` value and 2-tuple of `f32` values.
fn Bar() -> (i32, (f32, f32));

fn Foo() -> i64 {
  // Pattern in `var` declaration:
  var (p: i64, _: auto) = Bar();
  return p;
}

Where _ is used for ignore. This seems like what is going to be used elsewhere.

Methods

Can have static and member functions. The member functions are part of the [] like syntax. Self is the containing type. me is used instead of this. me doesn't have to be a pointer, it could be a value type.

fn Point.Offset[addr me: Self*](dx: i32, dy: i32) {
  me->x += dx;
  me->y += dy;
}

Uses keywords base and abstract. Classes are final by default. If abstract they cannot be instanciated. By default classes are final. Uses virtual abstract and impl on method definitions.

Tagged Unions

Described as choice types. Implemented in a similar style to say rusts enums.

Uses the choice name to introduce. If there is no associated data, acts like an enum.

Resources

Carbon Discussion from Odin Creator

Videos