Google’s Carbon Programming Language – In a Nutshell

A group of developers at Google and other organizations believe it is. The group is behind an experimental language called google's Carbon programming language, which offers interoperability with C++ while overcoming purported difficulties in improving the legacy language.

Know Google’s Carbon Programming Language’s syntax in this Tutorial.

According to Google developer Chandler Carruth, Carbon could serve as a successor language to C++. The documentation declares that Carbon “is designed around interoperability with C++ as well as large-scale adoption and migration for existing C++ codebases and developers”.

The documentation states that Carbon “is designed around interoperability with C++ as well as large-scale adoption and migration for existing C++ codebases and developers”.

This article will provide a practical coding introduction to the Carbon programming language but it won’t discuss why someone might need it.

Must read:

Introduction to Standard C++ : In a Nutshell #1

How does HTTPS Work?

Algorithms Analysis and why it is important? : DSA

Getting Started

This Getting Started part has two semi-parts:

  • Tooling up
  • Language Basics

If you have already installed Carbon, feel free to skip to Language Basics, otherwise start with Tooling.

In the Tooling section, we will go over the following to set up our environment:

  • Homebrew
  • Bazel
  • LLVM
  • Clone Carbon language
  • Run the Explorer

You can find the identical approach in Getting Started.

Here, I will add some context and a short description for the ones who approached the language and the tooling for the first time.

After the installation, move forward with some practical examples in the Language Basics section.

Carbon Language: Tooling

tooling up illustrations

1. Homebrew

Homebrew is a package manager that might be already installed on your computer. Run brew --versionto check if you have it already.

If this is not the case, you can install Homebrew on macOS, Linux, and Windows (through WSL).

2. Bazel

Bazel is an open-source build and test tool that scalably supports multi-language and multi-platform projects.

According to the documentation, “Bazel is Carbon’s standard build system. Bazelisk is recommended for installing Bazel”.

Run the below command to install Bazel:

brew install bazelisk

3. LLVM

You can see LLVM as a low-level virtual machine, but “LLVM has little to do with traditional virtual machines”. LLVM is used to compile and link Carbon as part of its build.

Run the following command to install LLVM:

brew install llvm

Be mindful that on macOS (not Linux), you should run the following command to update the PATH:

export PATH="$(brew --prefix llvm)/bin:${PATH}"

4. Clone Carbon Language

Run the following commands from an suitable folder.

They will clone and download the Carbon language code locally.

$ git clone https://github.com/carbon-language/carbon-lang
$ cd carbon-lang

5. Run the Explorer

Finally, we are ready to build and run the explorer.

The following code runs Bazel build tool so that it triggers the explorer code:

$ bazel run //explorer -- ./explorer/testdata/print/format_only.carbon

The explorer runs the actual code in the file at the designated location: ./explorer/testdata/print/format_only.carbon

The extension .carbon is necessary to let the interpreter know that the file contains carbon language.

Eventually, you should get a nice “Hello World” like the following.

Print of Hello World using Carbon Programming Language
Print of “Hello World” using Carbon Programming Language

Carbon Language: Language Basics

language basic explaining illustration

If you are familiar with C, C++, and similar languages you’ll be familiar with Carbon syntax as well.

Carbon is still testing and some things might change. The documentation itself states “Note that Carbon is not ready for use”. However, some design principles are less likely to change.

Let’s start by looking at the code in format_only.carbon

package ExplorerTest api;fn Main() -> i32 {
var s: auto = "Hello world!";
Print(s);
return 0;
}
  • The package keyword declares packages.
    This file contributes to the default library.
  • The fn introducer keyword declares functions.
    The code fn Main() -> i32 {...} declares a function named Main. The type of the return of Main() is i32 i.e. integer. Main returns zero.
  • The var keyword introduces a variable declaration.
    In the code above, s is the name of the variable, followed by a colon : , and the type. auto is used to infer the variable type automatically.
  • It is possible to declare constants by using the keyword let.
    While this might be confusing for developers coming from JavaScript, it will be smooth for devs coming from Swift.
  • Finally, you can add comments by adding two slashes //

This simple file gives us a good overview of Carbon syntax.

Let’s count some other pieces.

Primitives Types and Values

In Carbon, we can have the following primitives:

  • Boolean. It has two possible values: true and false.
  • Integer. Carbon contemplates signed and unsigned integers.
    Signed-integers can be i8, i16, i32, i64, i128, or i256. Unsigned integers can be u8, u16, u32, u64, u128, and u256.
  • Float. It is possible to use f16, f32, f64, and f128.
  • String. A byte sequence. String literals can be written on a single line using a double quotation mark (“) at the beginning and end of the string. Like “example”. Multi-line (or block string ) literals begin and end with three double quotation marks (“””). It is possible to use an escape sequence by prepending a backslash (\).

Functions Params and Return Types

Let’s extend the code to include another function:

package ExplorerTest api;// Return type is empty/void
fn AgeLogger(var age: i32) {
Print("Carbon is {0} years old", age);
}fn Main() -> i32 {
var s: auto = "Hello world!";
Print(s);
AgeLogger(0);
return 0;
}

The AgeLogger function takes one variable parameter named age of type i32.

However, since AgeLogger doesn’t return anything, we can simply skip the return type.

Return of functions in Carbon Programming Language
Return of functions in Carbon Programming Language

Thus, generally speaking, the syntax for a Carbon function is:

fn FunctionName(var param: type, ...) -> return type { ... }

The return type can be ignored when the return is empty or void.

Must read:

Introduction to Standard C++ : In a Nutshell #1

How does HTTPS Work?

Algorithms Analysis and why it is important? : DSA

Control Flow: If/Else

If you are coming from C++ or many other languages, this is nothing new.

As reported, “if and else provide conditional execution of statements”.

Let’s add this part to our example:

fn AgeLogger(var age: i32) {
if(age == 0){
Print("Carbon is {0} years old", age);
} else {
Print("Carbon is not {0} years old", age);
}
}

By adding some simple control flow, blocks of statements are executed conditionally. Otherwise, blocks of statements are executed sequentially.

Control Flow: While Loop

While the expression is true the loops continue.

I will include the following piece of code just after the if/else statements in the AgeLogger function.

var x: i32 = 0;
while (not (x == 3)) {
Print("I am number {0}", x);
x = x + 1;
}
Print("Done!");

I should say that in my case I couldn’t use while(x < 3) nor ++x. In both cases, I would get an error despite that syntax being shown in the documentation.

I didn’t understand why yet! So, if you figure it out, feel free to add the reason to the comments for everyone to know.

In any case, the outcome is:

Using While Loop In Carbon
Using While Loop In Carbon

Control Flow: For Loop

According to the documentation, forstatements support range-based looping. The example provided is pretty generic:

for (var name: String in names) {
Console.Print(name);
}

But I couldn’t get it to work:

var names: [String;3] = ("str1", "str2", "str3");
for (var name: String in names) {
Print(name);
}

While trying the above code, I kept getting “syntax error, unexpected VAR”, which I couldn’t understand as the keyword varis supposed to be there!

Unfortunately, I couldn’t find a working example anywhere.

Control Flow: Match

Once again, if you are coming from C or C+, this will be familiar.

matchchecks the value of an expression against the case declarations. In the following example, the value 2 is matched against the case declarations, and the string “Matching two” is returned.

fn TheMatcher() -> String {
Print("I am here");
var x: i32 = 2;
match (x) {
case (0) => {
return "Matching zero";
}
case (1) => {
return "Matching one";
}
case (2) => {
return "Matching two";
}
default => {
return "Matching none";
}
}
}

The last block is an optional defaultblock. The defaultblock can be added after the case declarations and it will be executed if none of the case declarations match.

Within the scope of control flow, it is worth mentioning break and continue. Briefly speaking:

  • break immediately ends a loop and Carbon “will continue starting from the end of the loop’s scope”.
  • continue immediately goes to the next loop.

Composite types

Before checking out composite types, I created a new file called composite_types.carbon that contains all the code from now onward.

Here is the new code:

package ExplorerTest api;fn Main() -> i32 {
var s: auto = "Hello composite types!";
Print(s);
return 0;
}

I will use it as a blank sheet to try out composite types.

a girl reading books illustration

Tuples: A tuple is a fixed-size collection of values, sometimes called coordinates, that can have different types. It is possible to access values by using their position.
The following code assigns the tuple (0, 1, 2, "omega") to x before printing the value ad index two and three.

fn UseTuples() {
var x: (i32, i32, i32, String) = (0, 1, 2, "omega");
Print("At index 2: {0}", x[2]);
Print(x[3]);
}// output:
At index 2: 2
omega

Struct types: Structural types allow to identify members by name instead of using their index or position.

  • Struct type example: {.name: String, .count: i32}
  • Struct value example: {.name = "John", .count = 4}
fn UseStruct() {
var data: auto = {.x_var = 3, .b_var = 2, .m_slope = 7};
var y: i32 = data.m_slope * data.x_var + data.b_var;
Print("y: {0}", y);
}// output:
y: 23

Pointer types: As in C and C++, a pointer is a variable that stores the memory address of an object. Carbon offers two pointer operations:

  • Dereference: given a pointer p, *p gives the value p points to as an l-value.
  • Address-of: given an l-value x, &x returns a pointer to x. There are no null pointers in Carbon.
fn Pointers() {
var x: i32 = 0;
Print("Initial x: {0}", x);

x = 10; // changes x to 10
Print("x is now {0}", x);

var y: i32* = &x; // returns pointer to x

*y = 5; // changes x to 5
Print("x is now {0}", x);
Print("y = {0}", *y);}// output:
Initial x: 0
x is now 10
x is now 5
y = 5
  • Arrays and slices: The generic syntax for the type of a Carbon array is [type, number of values].
    The following code assigns the type and values to an array that contains four integers.
var array: [i32; 4] = (0, 1, 2, 3);// It is possible to omit the number of values 
// when the size of the array can be inferred
var array: [i32;] = (0, 1, 2, 3);// access elements using square brakets
Print(array[0]);

Classes

It is possible to use classes to define data structures.

Use the class keyword before the name of the class to define a class.

The example class is named Widget and contains three fields declared by using the var keyword before the name of the field.

You also need to declare the type of each field.

class Widget {
var x: i32;
var y: i32;
var payload: String;
}

Classes can also contain functions, methods, alias, constants, and nested classes.

The following example expands to add a function that returns the sum of the two integer fields.

class Widget {
var x: i32;
var y: i32;
var payload: String;

fn Sum[me: Self]() -> i32 {
var total: i32 = me.x + me.y;
return total;
}
}

First, I expanded the class to include a sum function. The function sums the value of the field x to the value of the field y and returns the total.

Then, I updated Main() as follows:

fn Main() -> i32 {
var coolWidget: Widget = {.x = 6, .y = 7, .payload = "load"};
var total: i32 = coolWidget.Sum();
Print("Total sum {0}" , total);
return 0;
}// output:
Total sum 13

Generics

As in other languages, generics “allow Carbon constructs like functions and classes to be written with compile-time parameters”.

Usually, T is used to define a generic that can be any type.

The official documentation provides a nice example.

First, we have a Min function. The function has a type parameter Tthat can be any type that implements the Orderedinterface.

fn Min[T:! Ordered](x: T, y: T) -> T {
return if x <= y then x else y;
}

Then, they assign values to the variables a and b. Given that both variables are of type i32T is deduced to be i32.

var a: i32 = 1;
var b: i32 = 2;
Assert(Min(a, b) == 1);

In the same way, assigning String values to a and b renders T a type String.

var a: String = "abc";
var b: String = "xyz";
Assert(Min(a , b) == "abc");

Be aware that in my case I had issues parsing characters like <=. However, a more generic test where we only pass values to print works fine.

fn TestGenerics[T:! Type](x: T) -> T {
return x;
}// output:
i32: 0
String

Must read:

Introduction to Standard C++ : In a Nutshell #1

How does HTTPS Work?

Algorithms Analysis and why it is important? : DSA

Conclusion

Given the several hiccups I uncovered, it is safe to say “Note that Carbon is not ready for use”. This is in line with what is declared in the documentation.

Despite these things, it was fun to play around and test it a bit. It was particularly fun because it is new and still untried.

I hope you will feel the same!

For the sake of keeping this article an introduction, I had to overlook parts.

Share your love
Lamba Pankaj
Lamba Pankaj
Articles: 14

Leave a Reply

Your email address will not be published. Required fields are marked *

I'm not a robot *Time limit exceeded. Please complete the captcha once again.