Trying Carbon, Yet Another Programming Language
I already know what you’re thinking:
Oh great.
Another damn programming language.
When will this madness end?
Just when you thought you were caught up on technology trends, google says fuck it, and drops a new programming language. Like Javascript web frameworks, your skills are now obsolete and you will need to learn this new language if you ever hope on getting hired. The language is called Carbon and It’s designed to be a successor to C++.
🦀 Just Use Rust Bro⌗
The carbon developers make a valid point. They say if you can use Rust, you should. However, migrating an existing C++ codebase to the Rust ecosystem is not easy. Carbon was designed to make it easy to migrate from the C++ tooling to Carbon’s way of doing things.
- If you’re starting a new project from scratch, use Rust
- If you’re working with an existing C++ codebase, try Carbon
Carbon is very experimental at this point and time. It’s the new and shiny toy that you picked off the shelf. Probably not a good idea to base it on your startup yet.
Getting Started With Carbon⌗
I would prefer to use a sandbox environment to play around with carbon rather than install a bunch of dependencies i’m not going to use. Lets go ahead and create a Dockerfile:
# syntax=docker/dockerfile:1.3-labs
from ubuntu:latest
# Install dependencies
RUN <<EOF
apt-get update
apt-get upgrade -y
apt-get install -y \
curl git python3 clang-14 flex bison m4 lld \
llvm build-essential libc++-14-dev zlib1g-dev cmake
EOF
# Install bazelisk
RUN <<EOF
curl \
-L https://github.com/bazelbuild/bazelisk/releases/download/v1.12.0/bazelisk-linux-amd64 \
--output /usr/local/bin/bazel
chmod +x /usr/local/bin/bazel
EOF
# Clone Carbon lang
RUN git clone https://github.com/carbon-language/carbon-lang
WORKDIR /carbon-lang/
ENV CC=/usr/bin/clang-14
ENV CXX=/usr/bin/clang++-14
# fetch bazel depencies for carbon explorer
RUN bazel build //explorer
CMD [ "/bin/bash" ]
Note: The bazel dependency fetch takes a long time.
The carbon lang repository is under active development and commits are being pushed daily. Make sure you’re rebuilding the image on a daily basis to get the latest updates from the github repository.
docker build -t carbon-lang . --no-cache
Now that our image is built, lets run the hello world example:
$ docker run -it \
carbon-lang \
bazel run //explorer -- ./explorer/testdata/print/format_only.carbon
Starting local Bazel server and connecting to it...
INFO: Invocation ID: c14464d0-1f94-45e7-ba5b-a4a6b9e41090
INFO: Analyzed target //explorer:explorer (67 packages loaded, 1556 targets configured).
INFO: Found 1 target...
Target //explorer:explorer up-to-date:
bazel-bin/explorer/explorer
INFO: Elapsed time: 3.640s, Critical Path: 0.13s
INFO: 1 process: 1 internal.
INFO: Build completed successfully, 1 total action
INFO: Build completed successfully, 1 total action
Hello world!
result: 0
Lets, or at least try to, write some Carbon Code⌗
There’s a lot of example carbon programs in the explorer directory within Carbon’s repository.
── explorer
│ └── testdata
│ ├── addr
│ ├── alias
│ ├── array
│ ├── assign
│ ├── assoc_const
| ...
│ ├── print
│ ├── return
│ ├── returned_var
│ ├── string
│ ├── struct
│ ├── tuple
│ └── while
We are going to create a directory to store all of our carbon files, and mount them into the Carbon container. I will go over more about that later on in the article when we are ready to run our example program.
mkdir carbon-projects/
Being a successor to C++, I was curious to see Carbon’s take on OOP. Carbon’s Language Design is a good start to familiarize yourself with Carbon’s features. Here is an example Carbon program that categorizes motorcycles:
package ExplorerTest api;
class Motorcycle {
// Mutating method declaration
fn PowerToWeightRatio[addr me: Self*]();
var horsePower: i32;
var weight: i32;
}
// Out-of-line definition of method declared inline
fn Motorcycle.PowerToWeightRatio[addr me: Self*]() -> i32 {
return me->horsePower / me->weight;
}
fn Main() -> i32 {
var cbr600: Motorcycle = {.horsePower = 113, .weight = 422};
var sportster: Motorcycle = {.horsePower = 60, .weight = 553};
Print(cbr600.PowerToWeightRatio());
Print(sportster.PowerToWeightRatio());
return 0;
}
To run the example within the carbon docker container:
docker run \
-v $(pwd)/carbon-projects/:/carbon-lang/explorer/testdata \
carbon-lang \
bazel run //explorer -- ./explorer/testdata/motorcycles.carbon
Starting local Bazel server and connecting to it...
INFO: Invocation ID: fc454194-6cd6-40f4-bf19-d7cd780035de
Loading:
Loading: 0 packages loaded
Analyzing: target //explorer:explorer (1 packages loaded, 0 targets configured)
Analyzing: target //explorer:explorer (42 packages loaded, 131 targets configured)
INFO: Analyzed target //explorer:explorer (67 packages loaded, 1556 targets configured).
INFO: Found 1 target...
[6 / 15] [Prepa] BazelWorkspaceStatusAction stable-status.txt
Target //explorer:explorer up-to-date:
bazel-bin/explorer/explorer
INFO: Elapsed time: 5.026s, Critical Path: 0.12s
INFO: 1 process: 1 internal.
INFO: Build completed successfully, 1 total action
INFO: Running command line: bazel-bin/explorer/explorer ./explorer/testdata/motorcycles.carbon
INFO: Build completed successfully, 1 total action
COMPILATION ERROR: ./explorer/testdata/motorcycles.carbon:10: syntax error, unexpected PERIOD, expecting LEFT_PARENTHESIS
The language is too new, and there are still going to be a lot of bugs.
I tried running this example class in my Carbon container:
Starting local Bazel server and connecting to it...
INFO: Invocation ID: 93b9f439-1f13-4eb6-981d-32f15f3afe69
Loading:
Loading: 0 packages loaded
Analyzing: target //explorer:explorer (1 packages loaded, 0 targets configured)
Analyzing: target //explorer:explorer (61 packages loaded, 409 targets configured)
INFO: Analyzed target //explorer:explorer (67 packages loaded, 1556 targets configured).
INFO: Found 1 target...
[3 / 202] [Prepa] BazelWorkspaceStatusAction stable-status.txt
Target //explorer:explorer up-to-date:
bazel-bin/explorer/explorer
INFO: Elapsed time: 4.161s, Critical Path: 0.07s
INFO: 1 process: 1 internal.
INFO: Build completed successfully, 1 total action
INFO: Running command line: bazel-bin/explorer/explorer ./explorer/testdata/motorcycles.carbon
INFO: Build completed successfully, 1 total action
Stack trace:
#0 0x000055bad6ed678b backtrace (/root/.cache/bazel/_bazel_root/3f95225d66356108996d4189de132605/execroot/carbon/bazel-out/k8-fastbuild/bin/explorer/explorer+0x4f578b)
#1 0x000055bad727014b llvm::sys::PrintStackTrace(llvm::raw_ostream&, int) /proc/self/cwd/external/llvm-project/llvm/lib/Support/Unix/Signals.inc:569:13
#2 0x000055bad6f645d0 Carbon::Internal::ExitingStream::ExitingStream() /proc/self/cwd/./common/check_internal.h:35:3
#3 0x000055bad7123d5b Carbon::Parser::parse() /proc/self/cwd/explorer/syntax/parser.ypp:0:7
#4 0x000055bad711715d Carbon::Parser::operator()() /proc/self/cwd/bazel-out/k8-fastbuild/bin/explorer/syntax/parser.cpp:1029:5
#5 0x000055bad70f64b5 Carbon::ParseImpl(void*, Carbon::Arena*, std::__1::basic_string_view<char, std::__1::char_traits<char> >, bool) /proc/self/cwd/explorer/syntax/parse.cpp:31:60
#6 0x000055bad70f56f1 Carbon::Parse(Carbon::Arena*, std::__1::basic_string_view<char, std::__1::char_traits<char> >, bool) /proc/self/cwd/explorer/syntax/parse.cpp:63:28
#7 0x000055bad6f5ce72 index /usr/lib/llvm-14/bin/../include/c++/v1/variant:785:12
#8 0x000055bad6f5ce72 index /usr/lib/llvm-14/bin/../include/c++/v1/variant:1435:59
#9 0x000055bad6f5ce72 __holds_alternative<1UL, Carbon::Error, Carbon::AST> /usr/lib/llvm-14/bin/../include/c++/v1/variant:1461:14
#10 0x000055bad6f5ce72 holds_alternative<Carbon::AST, Carbon::Error, Carbon::AST> /usr/lib/llvm-14/bin/../include/c++/v1/variant:1467:10
#11 0x000055bad6f5ce72 ok /proc/self/cwd/./common/error.h:62:36
#12 0x000055bad6f5ce72 Carbon::Main(llvm::StringRef, int, char**) /proc/self/cwd/explorer/main.cpp:73:3
#13 0x000055bad6f5bf9c index /usr/lib/llvm-14/bin/../include/c++/v1/variant:785:12
#14 0x000055bad6f5bf9c index /usr/lib/llvm-14/bin/../include/c++/v1/variant:1435:59
#15 0x000055bad6f5bf9c __holds_alternative<1UL, Carbon::Error, Carbon::Success> /usr/lib/llvm-14/bin/../include/c++/v1/variant:1461:14
#16 0x000055bad6f5bf9c holds_alternative<Carbon::Success, Carbon::Error, Carbon::Success> /usr/lib/llvm-14/bin/../include/c++/v1/variant:1467:10
#17 0x000055bad6f5bf9c ok /proc/self/cwd/./common/error.h:62:36
#18 0x000055bad6f5bf9c Carbon::ExplorerMain(llvm::StringRef, int, char**) /proc/self/cwd/explorer/main.cpp:91:69
#19 0x000055bad6f5b411 main /proc/self/cwd/explorer/main_bin.cpp:0:10
#20 0x00007f9918ab1d90 (/lib/x86_64-linux-gnu/libc.so.6+0x29d90)
#21 0x00007f9918ab1e40 __libc_start_main (/lib/x86_64-linux-gnu/libc.so.6+0x29e40)
#22 0x000055bad6e9ac65 _start (/root/.cache/bazel/_bazel_root/3f95225d66356108996d4189de132605/execroot/carbon/bazel-out/k8-fastbuild/bin/explorer/explorer+0x4b9c65)
CHECK failure at explorer/syntax/parser.ypp:326: yystack_[0].value.as < std::string > ()[0] == 'i' && val == 32: Only i32 is supported for now: f32
Looks like theres issues with floats and classes that still need to be looked into. It seems like only ints are supported because whenever I used unsigned ints or floats, I got this error. Interestingly enough, the examples on the carbon github repo are using floats.
Let’s try another example. Here is a program that withdraws/deposits money into a bank account:
package ExplorerTest api;
class Person {
var name: String;
var bankaccount: i32;
}
fn incrementBankAccount(person: Person, deposit: i32) -> i32 {
Print("incrementing balance by: {0}", deposit );
var balance: i32 = 0;
balance = person.bankaccount + deposit;
return balance;
}
fn decrementBankAccount(person: Person, withdrawal: i32) -> i32 {
var balance: i32 = 0;
balance = person.bankaccount - withdrawal;
if (balance < 0) {
Print("Not enough funds");
return person.bankaccount;
}
Print("decrementing balance by: {0}", withdrawal );
return balance;
}
fn Main() -> i32 {
var joe: Person = { .name = "Joe", .bankaccount = 100 };
Print("Joes Balance Before: {0}", joe.bankaccount);
joe.bankaccount = incrementBankAccount(joe, 200);
Print("Joes Balance After: {0}", joe.bankaccount);
joe.bankaccount = decrementBankAccount(joe, 500);
return 0;
}
<
symbol:
Starting local Bazel server and connecting to it...
INFO: Invocation ID: 5d21b467-8fab-4b93-8110-a6d521fd4608
Loading:
Loading: 0 packages loaded
Analyzing: target //explorer:explorer (1 packages loaded, 0 targets configured)
Analyzing: target //explorer:explorer (41 packages loaded, 131 targets configured)
Analyzing: target //explorer:explorer (67 packages loaded, 1555 targets configured)
INFO: Analyzed target //explorer:explorer (67 packages loaded, 1556 targets configured).
INFO: Found 1 target...
[4 / 159] [Prepa] BazelWorkspaceStatusAction stable-status.txt
Target //explorer:explorer up-to-date:
bazel-bin/explorer/explorer
INFO: Elapsed time: 5.783s, Critical Path: 0.21s
INFO: 1 process: 1 internal.
INFO: Build completed successfully, 1 total action
INFO: Running command line: bazel-bin/explorer/explorer ./explorer/testdata/in-my-bank-account.carbon
INFO: Build completed successfully, 1 total action
COMPILATION ERROR: ./explorer/testdata/in-my-bank-account.carbon:18: invalid character '\x3C' in source file.
In Conclusion⌗
My experiment proved that carbon is still too fresh to even consider using. The parser is too quirky and many of the documented features of the language did not work for me. Maybe it’s because I was running within a sandboxed environment. The Carbon language is dependent on LLVM and maybe it behaves strangely within a container? 😞
This project interests me and I will keep an eye on it as development continues. 👀