Carbon Copy No.4

36 views
Skip to first unread message

Daniel 'Wolff' Dobson

unread,
Sep 5, 2024, 5:43:05 PM9/5/24
to anno...@carbon-lang.dev

Carbon Copy, September 2024

Here is the new Carbon Copy, your periodic update on the Carbon language!


Carbon Copy is designed for those people who want a high-level view of what's happening on the project. If you'd like to subscribe, you can join anno...@carbon-lang.dev. Carbon Copy should arrive roughly (sometimes very roughly) every other month.

Toolchain Progress

If you don't want to compile the toolchain yourself, Carbon is now releasing nightly builds.  Instructions are on the homepage; in short:


# A variable with the specific nightly version:

VERSION="$(date -d yesterday +0.0.0-0.nightly.%Y.%m.%d)"


# Get the release

wget https://github.com/carbon-language/carbon-lang/releases/download/v${VERSION}/carbon_toolchain-${VERSION}.tar.gz 


# Unpack the toolchain:

tar -xvf carbon_toolchain-${VERSION}.tar.gz


# Create a simple Carbon source file:

echo "fn Run() { Core.Print(42); }" > forty_two.carbon


# Compile to an object file:

./carbon_toolchain-${VERSION}/bin/carbon compile \

  --output=forty_two.o forty_two.carbon


# Install minimal system libraries used for linking. Note that installing `gcc`

# or `g++` for compiling C/C++ code with GCC will also be sufficient, these are

# just the specific system libraries Carbon linking still uses.

# sudo apt install libgcc-11-dev


# Link to an executable:

./carbon_toolchain-${VERSION}/bin/carbon link \

  --output=forty_two forty_two.o


# Run it:

./forty_two

This toolchain is not ready for actual use. Visit our releases page for your nightly experiments!

Spotlight: Pattern matching and tuples 

This issue, we're going to look at patterns. A design goal for Carbon is to have a single kind of pattern matching all across the language. This language is used in places like: 


  • name-binding declarations

  • function parameters

  • match statements


Each of these look similar, as they use subsets of the features of Carbons patterns. This spotlight will dig into each of these three uses of patterns. Along the way, we're going to explore Carbon's implementation of tuples for the examples.


Let's get started!

Tuples

So, how about tuples?  Tuples are a composite type that can contain an arbitrary number of values, which in turn can also be tuples. To declare the type of a tuple, you can create a tuple of types.


let a_tuple: (i32, bool) = (4, true);

// Note that tuple literals can have trailing commas

let a_tuple: auto = (0.5, false,);

// In fact, single-element tuples require trailing commas to 

// disambiguate grouping parentheses.

// This is an integer
let x: auto = (1 + 2);

// This is a 1-tuple

let y: auto = (1 + 2,);


// Tuples can be 0-element

let empty_tuple: auto = ();

// Or arbitrarily deep.

let nested_tuple: (i32, (f32, bool)) = (2, (3.0, true));


To index into a tuple, you can index with a constant expression.


let x: i32 = a_tuple.0;

let y: f32 = nested_tuple.1.0;

let z: bool = a_tuple.(2 - 1);


Tuples are used to return more than one value: 


fn AddAndSubtract(a:i32, b:i32) -> (i32, i32) {

  return (a+b, a-b);

Patterns

A pattern is an expression-like syntax that describes the structure of some value.


Patterns are allowed to have unknowns which may have names. These are called binding patterns, and similar such features appear in other languages. When a pattern is given a value (called the scrutinee), the pattern determines whether the scrutinee matches the pattern, and if so, determines the values of the bindings.


Here are some tuple binding patterns:


// This binds `a` to `a_tuple.0` and `b` to `a_tuple.1`

let (a: i32, b: bool) = a_tuple;


This will be reminiscent of C++ structured binding declarations. However, unlike C++ structured binding declarations, Carbon patterns let you specify the types of the individual bindings, and Carbon tuple patterns can be nested:


// Given existing variables a, b, c, d, e, and f

var (a_prime: auto, (b_prime: auto, c_prime: auto, d_prime: auto)) = (a, (b, c, (d, e, f)))


// a_prime == a

// b_prime == b 

// c_prime == c 

// d_prime == (d, e, f)


// Underscore (`_`) indicates "don't care"; you can use it more than once

let (first: bool, _: bool, third: bool, _:bool) = a_long_tuple;


You can also pattern match on structs.


let a_struct: auto = {.a = 1 as i32, .b = 2.0 as f32, .c as f32};


// This initializes x to a_struct.a and y to a_struct.b.

let {.a = x: i32, .b = y: f32, .c = z: f32} = a_struct;


// When the variable name is the same as the field name, you can use a

// shorthand, and you don't need to use every field:

let {a: i32, b: f32, _} = a_struct;

Patterns and var

In the previous Spotlight we discussed name-binding declarations.


let an_int: i32 = 2;

# a var declaration is short for "let var..."

var an_int_var: i32 = 2;


These two cases are different.

  • an_int is a value. It provides 2 when accessed, and is immutable. It may or may not be implemented as a copy or a reference, but that is transparent to its user.

  • an_int_var is a variable, which means it has explicit storage allocated to it and is mutable. Every var has its own storage and doesn't affect other variables.


an_int: i32 and an_int_var: i32 are trivial binding patterns that bind a single unknown to a single scrutinee.


var is actually a pattern operator, and when it's used to introduce a statement, it's a shorthand for let var. `var` available in any pattern context, including tuple patterns. var makes a mutable copy of the scrutinee, and then matches that copy against the operand of var. All binding patterns nested under a var pattern define variables rather than values.


// q is a variable, p and r are values

let (p: i32, (var q: f32, r: bool)) = nested_tuple;


// q and r are both variables, and deep copies of parts of nested_tuple

let (p: i32, var (q: f32, r: bool)) = nested_tuple;

Function signatures

In Carbon, function signatures look like this:


fn F(a: i32, b: bool);

fn DoSomething(arg: i32); 


You might notice that this looks familiar. Function signatures are patterns. A toplevel function signature has to be a tuple pattern.  


Side note: You might remember from above that single-element tuple literals require a trailing comma to avoid an ambiguity with grouping parentheses. In a function signature, there is no ambiguity, so a trailing comma is not required.


And var can make an appearance in function signatures, too, doing what you'd expect:


fn F(a: i32, var mutable_copy_b: bool);

match and patterns

In C++ you can use a switch statement to execute different code depending on which of several values a given expression has. Carbon's match statement is similar:


match (a) {

  case 5 => { DoSomething(); } 

  case 9 => {} { DoSomethingElse(); }

  default => {} { DoDefault(); }

}


Carbon's match is more general than switch. Unlike C++, the cases can be patterns, with all the features we introduced above. Furthermore, the patterns can be refutable, meaning they don't have to match all possible values: if one case doesn't match the scrutinee, the match statement will just move on to the next one.


match (a_tuple) {

  case (1, _: auto) => {

    // This block runs if `a_tuple == (1, [anything])`.

  }

  case (x: i32, false) => {

    // This block runs if `a_tuple.1 == false`. `x` is bound to `a_tuple.0`.

    // It does not run if a_tuple.0 == 1, as the first case will match before

    // reaching this case.

  }

  case (x: i32, y: bool) => {

    // The first case that matches will be run, so this block runs if

    // `a_tuple.1 == true and a_tuple.0 != 1`.

  }

}


This last example gets a little outside of this topic, as it uses templates, but it's also a neat outcome of the consistency of using patterns. (Generics will be the topic of future spotlights. There's so much to talk about!) In this example (which contains speculative features), the case executed is determined by the type of x, not its value:


fn TypeName[template T:! Type](x: T) -> String {

  match (x) {

    case _: i32 => { return "int"; }

    case _: bool => { return "bool"; }

    case _: auto* => { return "pointer"; }

    default => { return "unknown"; }

  }

}

Conclusion 

Why do we think patterns are so important?


Carbon uses pattern features and syntax across the language, from local variables to function signatures to match/case statements. In C++ these each of these three different features have their own rules.  


By treating them as aspects of a single concept in Carbon, we intend to give programmers fewer differences and distinctions to learn and remember. This consistency can even help with tasks like refactoring code; switching from a match statement to an overloaded function should be very straightforward. Pattern concepts like var are the same in function definitions and match and in let.


Read more about patterns (with footnotes!) in our toplevel design doc.

Recent proposals

In progress since last newsletter, now approved & merged:


  • Singular extern declarations #3980


New since last newsletter, now approved & merged:


  • Change operator precedence #4075

  • Establish toolchain and language versioning #4105

Carbon at Conferences

Recently

 

Upcoming

Other notes

If you want more current discussion, check out the weekly meeting notes from the Carbon Weekly Sync.  Please see the archive for notes from earlier weeks and months.

Wrap-up

Don't forget to subscribe! You can join anno...@carbon-lang.dev. If you have comments or would like to contribute to future editions of Carbon Copy, please reach out. And, always, join us any way you can!


Isotopically yours,

Geoffrey, Josh, Wolff, and the Carbon team

Reply all
Reply to author
Forward
0 new messages