Interesting, but not really mature enough yet to take too seriously.
[addr me: Self*]
addr
and Self*
?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.
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.
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.
Uses a somewhat unusual =>
.
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++.
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.
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 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?
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.
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.
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 let
Fod 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;
}
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.
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.
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.