Carbon Development Skill
Overview
Carbon is an experimental successor language to C++, designed by Google to address modern development needs while maintaining seamless bidirectional interoperability with existing C++ codebases. Carbon aims to provide memory safety, modern syntax, and better developer ergonomics while allowing incremental migration from C++.
Key Characteristics:
- Memory safety with compile-time guarantees (following Rust's direction)
- Seamless C++ interoperability without runtime overhead
- Modern generics system with both checked and template generics
- Pattern matching for control flow
- Explicit, lightweight error handling
- Designed for large-scale adoption and migration
Current Status (2025):
- Experimental language under active development
- 0.1 milestone targeted for end of 2026 (ambitious goal)
- Major focus on C++ interop demo and memory safety design
- Built with Bazel, compiler evaluation phase
Quick Reference
Basic Syntax
// Package declaration
package Sample api;
// Import statement
import Main;
// Function definition
fn Add(a: i32, b: i32) -> i32 {
return a + b;
}
// Main entry point
fn Main() -> i32 {
var result: i32 = Add(5, 10);
Print("Result: {0}", result);
return 0;
}
Variable Declarations
// Mutable variable
var x: i32 = 42;
// Immutable variable (const)
let y: i32 = 100;
// Type inference
var z: auto = 42; // inferred as i32
// Uninitialized (must initialize before use)
var w: i32;
w = 50;
Basic Types
// Integer types
var a: i8 = 127;
var b: i16 = 32767;
var c: i32 = 2147483647;
var d: i64 = 9223372036854775807;
// Unsigned integers
var ua: u8 = 255;
var ub: u16 = 65535;
var uc: u32 = 4294967295;
var ud: u64 = 18446744073709551615;
// Floating point
var f: f32 = 3.14;
var g: f64 = 2.718281828;
// Boolean
var flag: bool = true;
// String
var message: String = "Hello, Carbon!";
// Type alias
alias MyInt = i32;
Functions
// Basic function
fn Greet(name: String) -> String {
return "Hello, " + name;
}
// Multiple parameters
fn Calculate(x: i32, y: i32, operation: String) -> i32 {
if (operation == "add") {
return x + y;
}
return x - y;
}
// No return value (void)
fn PrintMessage(msg: String) {
Print(msg);
}
// Early return
fn Divide(a: f64, b: f64) -> Optional(f64) {
if (b == 0.0) {
return Optional.None;
}
return Optional.Some(a / b);
}
Control Flow
// If-else
if (condition) {
// do something
} else if (other_condition) {
// do something else
} else {
// default case
}
// While loop
var i: i32 = 0;
while (i < 10) {
Print("{0}", i);
i = i + 1;
}
// For loop
for (var j: i32 = 0; j < 10; j = j + 1) {
Print("{0}", j);
}
// Break and continue
while (true) {
if (should_exit) {
break;
}
if (should_skip) {
continue;
}
// normal iteration
}
Pattern Matching
// Match statement (replaces switch)
match (value) {
case 0 => {
Print("Zero");
}
case 1 => {
Print("One");
}
case 2 | 3 | 4 => {
Print("Two, three, or four");
}
default => {
Print("Something else");
}
}
// Match with patterns
fn Classify(x: i32) -> String {
return match (x) {
case 0 => "zero",
case 1 => "one",
case n if n < 0 => "negative",
case n if n > 0 and n < 10 => "small positive",
default => "large positive",
};
}
// Destructuring in match
match (result) {
case Optional.Some(value) => {
Print("Got value: {0}", value);
}
case Optional.None => {
Print("No value");
}
}
Core Concepts
Memory Safety
Carbon's memory safety strategy follows Rust's direction, using the type system for compile-time guarantees without runtime overhead.
Safety Categories:
Spatial Memory Safety: Protects against out-of-bounds access
- Array boundary checks
- Invalid pointer dereferencing
Temporal Memory Safety: Protects against use-after-free
- Heap use-after-free
- Stack use-after-return
Build Modes:
// Debug build: immediate runtime detection
var array: [i32; 5] = [1, 2, 3, 4, 5];
var index: i32 = 10;
// In debug mode: caught immediately
var value: i32 = array[index];
// Performance build: undefined behavior if violated
// Optimizer assumes no overflow/out-of-bounds
var x: i32 = 2147483647;
x = x + 1; // UB in performance mode
// Hardened build: safe but potentially incorrect
// Overflow won't crash but may produce wrong result
// Or program will abort safely
Uninitialized State Tracking:
// Carbon tracks initialization better than C++
var x: i32;
// Print(x); // Error: use of uninitialized variable
x = 42;
Print(x); // OK: initialized before use
// Conditional initialization
var y: i32;
if (condition) {
y = 10;
} else {
y = 20;
}
// OK: y is initialized in all paths
Print(y);
Dynamic Bounds Checking:
// APIs designed for safety
fn SafeAccess(array: [i32], index: i32) -> Optional(i32) {
if (index < 0 or index >= array.size()) {
return Optional.None;
}
return Optional.Some(array[index]);
}
// Usage
match (SafeAccess(my_array, idx)) {
case Optional.Some(value) => {
Print("Value: {0}", value);
}
case Optional.None => {
Print("Index out of bounds");
}
}
C++ Interoperability
Carbon's primary design goal is seamless, bidirectional interoperability with C++.
Philosophy:
- Zero runtime overhead for interop calls
- No custom bridge code for simple types/functions
- Call Carbon from C++ and vice versa
- Works even with non-Carbon-aware C++ toolchains
- Incremental migration support
Calling C++ from Carbon:
// Import C++ header
import Cpp library "mylib.h";
fn UseCppFunction() {
// Call C++ function directly
var result: i32 = Cpp.MyCppFunction(42);
// Use C++ class
var obj: Cpp.MyCppClass = Cpp.MyCppClass();
obj.Method();
}
// C++ types are accessible
fn ProcessVector(vec: Cpp.std.vector(i32)) {
for (var i: i32 = 0; i < vec.size(); i = i + 1) {
Print("{0}", vec[i]);
}
}
Calling Carbon from C++:
// C++ code
#include "carbon_module.h"
int main() {
// Call Carbon function from C++
int result = Carbon::MyFunction(10, 20);
// Use Carbon class
Carbon::MyClass obj;
obj.DoSomething();
return 0;
}
Migration Strategy:
// Phase 1: Minimal migration
// Auto-migrate C++ to interop-focused Carbon dialect
// Maintains C++ semantics, minimal code changes
// Phase 2: Incremental refactoring
// Gradually adopt Carbon idioms
// Introduce memory safety features
// Modernize APIs and patterns
// Example: C++ -> Carbon migration
// C++ original:
// void processData(std::vector<int>& data) {
// for (auto& item : data) {
// item *= 2;
// }
// }
// Phase 1: Direct migration
fn ProcessData(data: Cpp.std.vector(i32)*) {
var i: i32 = 0;
while (i < data->size()) {
(*data)[i] = (*data)[i] * 2;
i = i + 1;
}
}
// Phase 2: Carbon idioms
fn ProcessData(data: [i32]*) {
for (var i: i32 = 0; i < data.size(); i = i + 1) {
data[i] = data[i] * 2;
}
}
Safety with Interop:
// Carbon code has safety guarantees
fn SafeCarbonFunction(x: i32) -> i32 {
// Bounds checked, initialization verified
var array: [i32; 5] = [1, 2, 3, 4, 5];
return array[x % 5]; // Safe modulo operation
}
// C++ interop accepts higher risk
fn CallUnsafeCpp() {
// C++ doesn't have same safety mechanisms
// Carbon calling C++ accepts this risk
unsafe {
Cpp.LegacyFunction();
}
}
// Mitigation strategies
fn SafeInterop(data: Cpp.std.vector(i32)) -> Optional(i32) {
// Add runtime checks around C++ data
if (data.empty()) {
return Optional.None;
}
return Optional.Some(data[0]);
}
Generics System
Carbon provides both checked generics and template generics for different use cases.
Checked Generics:
// Basic generic function
fn GenericExample[T:! Type](x: T) -> T {
return x;
}
// Usage with type inference
fn Main() -> i32 {
var int_val: i32 = GenericExample(42);
var str_val: String = GenericExample("hello");
return 0;
}
// Generic with interface constraint
interface Comparable {
fn Compare[self: Self](other: Self) -> i32;
}
fn Max[T:! Comparable](a: T, b: T) -> T {
if (a.Compare(b) > 0) {
return a;
}
return b;
}
Interfaces:
// Define an interface
interface Vector {
fn Add[self: Self](b: Self) -> Self;
fn Scale[self: Self](v: f64) -> Self;
}
// Implement interface for a type
class Vec2 {
var x: f64;
var y: f64;
}
impl Vec2 as Vector {
fn Add[self: Self](b: Self) -> Self {
return Vec2{.x = self.x + b.x, .y = self.y + b.y};
}
fn Scale[self: Self](v: f64) -> Self {
return Vec2{.x = self.x * v, .y = self.y * v};
}
}
// Generic function using interface
fn Transform[T:! Vector](vec: T, factor: f64) -> T {
return vec.Scale(factor);
}
Template Generics:
// Template generic (C++ style)
// Not checked at definition, checked at instantiation
fn TemplateFunc[template T:! Type](x: T) -> T {
// Can use any operations on T
// Errors shown at instantiation site
return x * 2 + 1;
}
// Useful for C++ interop
fn ProcessCppContainer[template T:! Type](container: T) {
// Works with any C++ container-like type
for (var i: i32 = 0; i < container.size(); i = i + 1) {
Print("{0}", container[i]);
}
}
Advantages of Checked Generics:
// Checked generics catch errors at definition
fn BrokenGeneric[T:! Type](x: T) -> T {
// Error: Type doesn't have '+' operator
// return x + x; // Caught immediately
}
// Fix: add constraint
interface Addable {
fn Add[self: Self](other: Self) -> Self;
}
fn WorkingGeneric[T:! Addable](x: T) -> T {
return x.Add(x); // OK: constraint guarantees Add exists
}
// Better error messages
fn UseGeneric() {
var s: String = "hello";
// Error: String doesn't implement Addable
// var result: String = WorkingGeneric(s);
// Clear message about missing interface
}
Generic Types:
// Generic class
class Container[T:! Type] {
var data: [T];
fn Add[self: Self*](item: T) {
self->data.push_back(item);
}
fn Get[self: Self](index: i32) -> Optional(T) {
if (index < 0 or index >= self.data.size()) {
return Optional.None;
}
return Optional.Some(self.data[index]);
}
}
// Usage
fn Main() -> i32 {
var int_container: Container(i32) = Container(i32)();
int_container.Add(10);
int_container.Add(20);
match (int_container.Get(0)) {
case Optional.Some(value) => {
Print("First: {0}", value);
}
case Optional.None => {
Print("Empty");
}
}
return 0;
}
Type System
Primitive Types:
// Sized integers (guaranteed size)
i8, i16, i32, i64 // Signed
u8, u16, u32, u64 // Unsigned
// Floating point
f32, f64
// Boolean
bool
// String
String
// Pointer types
T* // Mutable pointer
const T* // Const pointer
// Array types
[T; N] // Fixed-size array
[T] // Dynamic array (slice)
Type Aliases:
// Simple alias
alias IntPtr = i32*;
alias Callback = fn(i32) -> i32;
// Generic alias
alias Result[T:! Type, E:! Type] =
variant { Ok(T), Err(E) };
// Usage
fn Divide(a: i32, b: i32) -> Result(i32, String) {
if (b == 0) {
return Result.Err("Division by zero");
}
return Result.Ok(a / b);
}
Sum Types (Variants):
// Define a sum type
variant Option[T:! Type] {
Some(T),
None
}
// Usage
fn Find(array: [i32], target: i32) -> Option(i32) {
for (var i: i32 = 0; i < array.size(); i = i + 1) {
if (array[i] == target) {
return Option.Some(i);
}
}
return Option.None;
}
// Pattern matching on variants
fn UseOption(opt: Option(i32)) {
match (opt) {
case Option.Some(value) => {
Print("Found: {0}", value);
}
case Option.None => {
Print("Not found");
}
}
}
Classes:
// Class definition
class Point {
var x: f64;
var y: f64;
// Constructor
fn Create(x_val: f64, y_val: f64) -> Self {
return Point{.x = x_val, .y = y_val};
}
// Method
fn Distance[self: Self](other: Self) -> f64 {
var dx: f64 = self.x - other.x;
var dy: f64 = self.y - other.y;
return Math.Sqrt(dx * dx + dy * dy);
}
// Mutable method
fn Move[self: Self*](dx: f64, dy: f64) {
self->x = self->x + dx;
self->y = self->y + dy;
}
}
// Usage
fn Main() -> i32 {
var p1: Point = Point.Create(0.0, 0.0);
var p2: Point = Point.Create(3.0, 4.0);
var dist: f64 = p1.Distance(p2);
Print("Distance: {0}", dist);
p1.Move(1.0, 1.0);
return 0;
}
Inheritance and Composition:
// Base class
class Shape {
fn Area[self: Self] -> f64;
}
// Derived class
class Circle {
var radius: f64;
base: Shape;
}
impl Circle as Shape {
fn Area[self: Self] -> f64 {
return 3.14159 * self.radius * self.radius;
}
}
// Composition
class Rectangle {
var width: f64;
var height: f64;
}
class ColoredRectangle {
var rect: Rectangle;
var color: String;
fn Area[self: Self] -> f64 {
return self.rect.width * self.rect.height;
}
}
Error Handling
Carbon uses explicit, statically-typed error handling without exceptions.
Basic Error Handling:
// Return sum type for errors
variant Result[T:! Type, E:! Type] {
Ok(T),
Err(E)
}
// Function that can fail
fn ParseInt(s: String) -> Result(i32, String) {
if (s.IsEmpty()) {
return Result.Err("Empty string");
}
// ... parsing logic
return Result.Ok(42);
}
// Handle errors
fn UseParseInt(input: String) {
match (ParseInt(input)) {
case Result.Ok(value) => {
Print("Parsed: {0}", value);
}
case Result.Err(error) => {
Print("Error: {0}", error);
}
}
}
Optional Type:
// Built-in optional type
variant Optional[T:! Type] {
Some(T),
None
}
// Function returning optional
fn GetConfig(key: String) -> Optional(String) {
if (ConfigExists(key)) {
return Optional.Some(GetConfigValue(key));
}
return Optional.None;
}
// Unwrap with default
fn GetOrDefault(opt: Optional(i32), default: i32) -> i32 {
return match (opt) {
case Optional.Some(value) => value,
case Optional.None => default,
};
}
Error Propagation:
// Error propagation operator (like Rust's ?)
// Expected in future Carbon versions
fn ProcessData() -> Result(i32, String) {
var data: String = ReadFile("data.txt")?;
var value: i32 = ParseInt(data)?;
return Result.Ok(value * 2);
}
// Manual propagation (current approach)
fn ProcessDataManual() -> Result(i32, String) {
var file_result: Result(String, String) = ReadFile("data.txt");
match (file_result) {
case Result.Err(e) => { return Result.Err(e); }
case Result.Ok(data) => {
var parse_result: Result(i32, String) = ParseInt(data);
match (parse_result) {
case Result.Err(e) => { return Result.Err(e); }
case Result.Ok(value) => {
return Result.Ok(value * 2);
}
}
}
}
}
Error Context:
// Enriching errors with context
variant Error {
IoError(String),
ParseError(String, i32), // message, line number
ValidationError(String)
}
fn ReadConfig(path: String) -> Result(Config, Error) {
var content_result: Result(String, String) = ReadFile(path);
var content: String = match (content_result) {
case Result.Err(e) => {
return Result.Err(Error.IoError(
"Failed to read " + path + ": " + e
));
}
case Result.Ok(c) => c,
};
// Continue processing...
return Result.Ok(config);
}
No Exceptions:
// Carbon does NOT have exceptions
// No try-catch blocks
// No throw statements
// Errors are explicit in function signatures
// This forces explicit error handling
fn RiskyOperation() -> Result(i32, String) {
// Must return Result, can't throw
if (something_wrong) {
return Result.Err("Something went wrong");
}
return Result.Ok(42);
}
// Caller must handle errors
fn Caller() {
var result: Result(i32, String) = RiskyOperation();
// Can't ignore the error - must handle it
match (result) {
case Result.Ok(value) => { /* use value */ }
case Result.Err(e) => { /* handle error */ }
}
}
Build System
Bazel-Based Build:
# BUILD file for Carbon project
load("@rules_carbon//carbon:defs.bzl", "carbon_library", "carbon_binary")
carbon_library(
name = "mylib",
srcs = ["mylib.carbon"],
deps = [
"//other:library",
],
)
carbon_binary(
name = "myapp",
srcs = ["main.carbon"],
deps = [
":mylib",
],
)
Compiler Usage:
# Compile Carbon source
carbon compile main.carbon
# Verbose output
carbon -v compile main.carbon
# Specify output
carbon compile main.carbon -o output.o
# Check syntax only
carbon check main.carbon
# Run different compilation phases
carbon compile --phase=parse main.carbon
carbon compile --phase=check main.carbon
carbon compile --phase=lower main.carbon
Project Structure:
my_carbon_project/
├── BUILD # Bazel build file
├── WORKSPACE # Bazel workspace
├── src/
│ ├── main.carbon # Main entry point
│ ├── lib.carbon # Library code
│ └── utils.carbon # Utilities
├── tests/
│ ├── BUILD
│ └── test_lib.carbon # Tests
└── api/
└── public.carbon # Public API
Package System:
// Package declaration
package MyProject api;
// Import from same package
import .Utils;
// Import from other package
import OtherProject;
// Import specific symbols
import OtherProject.{Function, Type};
// Use imported code
fn Main() -> i32 {
Utils.Helper();
OtherProject.Function();
return 0;
}
Common Patterns
Resource Management
// RAII-style resource management
class File {
var handle: FileHandle;
fn Open(path: String) -> Result(Self, String) {
var handle_result: Result(FileHandle, String) =
OpenFileHandle(path);
match (handle_result) {
case Result.Err(e) => {
return Result.Err(e);
}
case Result.Ok(h) => {
return Result.Ok(File{.handle = h});
}
}
}
fn Read[self: Self*](buffer: [u8]*) -> Result(i32, String) {
return ReadFromHandle(self->handle, buffer);
}
// Destructor
destructor [self: Self*] {
CloseFileHandle(self->handle);
}
}
// Usage - automatic cleanup
fn ProcessFile(path: String) -> Result(i32, String) {
var file_result: Result(File, String) = File.Open(path);
match (file_result) {
case Result.Err(e) => {
return Result.Err(e);
}
case Result.Ok(file) => {
var buffer: [u8; 1024] = [0; 1024];
var read_result: Result(i32, String) =
file.Read(&buffer);
// file destroyed automatically here
return read_result;
}
}
}
Builder Pattern
class ConfigBuilder {
var host: Optional(String) = Optional.None;
var port: Optional(i32) = Optional.None;
var timeout: Optional(i32) = Optional.None;
fn New() -> Self {
return ConfigBuilder{};
}
fn SetHost[self: Self*](host: String) -> Self* {
self->host = Optional.Some(host);
return self;
}
fn SetPort[self: Self*](port: i32) -> Self* {
self->port = Optional.Some(port);
return self;
}
fn SetTimeout[self: Self*](timeout: i32) -> Self* {
self->timeout = Optional.Some(timeout);
return self;
}
fn Build[self: Self] -> Result(Config, String) {
var h: String = match (self.host) {
case Optional.Some(v) => v,
case Optional.None => {
return Result.Err("Host is required");
}
};
var p: i32 = GetOrDefault(self.port, 8080);
var t: i32 = GetOrDefault(self.timeout, 30);
return Result.Ok(Config{
.host = h,
.port = p,
.timeout = t
});
}
}
// Usage
fn CreateConfig() -> Result(Config, String) {
var builder: ConfigBuilder = ConfigBuilder.New();
return builder
.SetHost("localhost")
.SetPort(3000)
.SetTimeout(60)
.Build();
}
Iterator Pattern
interface Iterator {
fn Next[self: Self*] -> Optional(i32);
}
class RangeIterator {
var current: i32;
var end: i32;
}
impl RangeIterator as Iterator {
fn Next[self: Self*] -> Optional(i32) {
if (self->current >= self->end) {
return Optional.None;
}
var value: i32 = self->current;
self->current = self->current + 1;
return Optional.Some(value);
}
}
fn Range(start: i32, end: i32) -> RangeIterator {
return RangeIterator{.current = start, .end = end};
}
// Usage
fn Main() -> i32 {
var iter: RangeIterator = Range(0, 10);
while (true) {
match (iter.Next()) {
case Optional.Some(value) => {
Print("{0}", value);
}
case Optional.None => {
break;
}
}
}
return 0;
}
Visitor Pattern
// Define visitor interface
interface NodeVisitor {
fn VisitNumber[self: Self*](value: i32);
fn VisitString[self: Self*](value: String);
}
// Define visitable nodes
variant Node {
Number(i32),
String(String)
}
fn AcceptVisitor[V:! NodeVisitor](node: Node, visitor: V*) {
match (node) {
case Node.Number(value) => {
visitor->VisitNumber(value);
}
case Node.String(value) => {
visitor->VisitString(value);
}
}
}
// Implement concrete visitor
class PrintVisitor {}
impl PrintVisitor as NodeVisitor {
fn VisitNumber[self: Self*](value: i32) {
Print("Number: {0}", value);
}
fn VisitString[self: Self*](value: String) {
Print("String: {0}", value);
}
}
// Usage
fn Main() -> i32 {
var nodes: [Node] = [
Node.Number(42),
Node.String("hello"),
Node.Number(100)
];
var visitor: PrintVisitor = PrintVisitor{};
for (var i: i32 = 0; i < nodes.size(); i = i + 1) {
AcceptVisitor(nodes[i], &visitor);
}
return 0;
}
Factory Pattern
interface Shape {
fn Area[self: Self] -> f64;
fn Perimeter[self: Self] -> f64;
}
class Circle {
var radius: f64;
}
impl Circle as Shape {
fn Area[self: Self] -> f64 {
return 3.14159 * self.radius * self.radius;
}
fn Perimeter[self: Self] -> f64 {
return 2.0 * 3.14159 * self.radius;
}
}
class Rectangle {
var width: f64;
var height: f64;
}
impl Rectangle as Shape {
fn Area[self: Self] -> f64 {
return self.width * self.height;
}
fn Perimeter[self: Self] -> f64 {
return 2.0 * (self.width + self.height);
}
}
// Factory function
fn CreateShape(shape_type: String, params: [f64])
-> Optional(variant {Circle, Rectangle})
{
if (shape_type == "circle" and params.size() >= 1) {
return Optional.Some(Circle{.radius = params[0]});
} else if (shape_type == "rectangle" and params.size() >= 2) {
return Optional.Some(Rectangle{
.width = params[0],
.height = params[1]
});
}
return Optional.None;
}
Migration from C++
Common C++ to Carbon Translations
Headers and Includes:
// C++
#include <vector>
#include <string>
#include "myheader.h"
// Carbon
import Cpp library "vector";
import Cpp library "string";
import .MyModule;
Class Definition:
// C++
class MyClass {
public:
MyClass(int x, int y) : x_(x), y_(y) {}
int getX() const { return x_; }
void setX(int x) { x_ = x; }
int compute() const {
return x_ * y_;
}
private:
int x_;
int y_;
};
// Carbon
class MyClass {
var x: i32;
var y: i32;
fn Create(x: i32, y: i32) -> Self {
return MyClass{.x = x, .y = y};
}
fn GetX[self: Self] -> i32 {
return self.x;
}
fn SetX[self: Self*](x: i32) {
self->x = x;
}
fn Compute[self: Self] -> i32 {
return self.x * self.y;
}
}
Templates to Generics:
// C++
template<typename T>
T max(T a, T b) {
return (a > b) ? a : b;
}
// Carbon - checked generic
interface Comparable {
fn Compare[self: Self](other: Self) -> i32;
}
fn Max[T:! Comparable](a: T, b: T) -> T {
if (a.Compare(b) > 0) {
return a;
}
return b;
}
// Or template generic for C++ compatibility
fn MaxTemplate[template T:! Type](a: T, b: T) -> T {
if (a > b) {
return a;
}
return b;
}
Smart Pointers:
// C++
std::unique_ptr<MyClass> obj =
std::make_unique<MyClass>(10, 20);
std::shared_ptr<MyClass> shared =
std::make_shared<MyClass>(10, 20);
// Carbon - ownership via move semantics
var obj: MyClass = MyClass.Create(10, 20);
var moved_obj: MyClass = obj; // Move ownership
// Reference counting (if needed)
var shared: Shared(MyClass) = Shared.New(MyClass.Create(10, 20));
Exceptions to Results:
// C++
int divide(int a, int b) {
if (b == 0) {
throw std::runtime_error("Division by zero");
}
return a / b;
}
try {
int result = divide(10, 0);
} catch (const std::exception& e) {
std::cerr << "Error: " << e.what() << std::endl;
}
// Carbon
fn Divide(a: i32, b: i32) -> Result(i32, String) {
if (b == 0) {
return Result.Err("Division by zero");
}
return Result.Ok(a / b);
}
match (Divide(10, 0)) {
case Result.Ok(value) => {
Print("Result: {0}", value);
}
case Result.Err(error) => {
Print("Error: {0}", error);
}
}
Namespaces to Packages:
// C++
namespace MyNamespace {
namespace Utils {
void helper() { }
}
}
MyNamespace::Utils::helper();
// Carbon
package MyProject.Utils api;
fn Helper() { }
// Usage
import MyProject.Utils;
Utils.Helper();
Migration Checklist
Phase 1: Initial Setup
- Set up Carbon toolchain and build system (Bazel)
- Identify C++ libraries to keep vs migrate
- Create interop layer for C++ dependencies
- Set up testing infrastructure
Phase 2: Incremental Migration
- Start with leaf modules (no dependencies)
- Migrate simple utility functions first
- Convert headers to Carbon packages
- Update build files to include Carbon sources
- Ensure tests pass with mixed C++/Carbon codebase
Phase 3: Refactoring
- Replace raw pointers with Carbon ownership
- Convert exceptions to Result types
- Adopt Carbon idioms (match, generics)
- Add memory safety features
- Improve error handling
Phase 4: Optimization
- Profile mixed codebase
- Identify performance bottlenecks
- Optimize hot paths
- Reduce C++ interop overhead where possible
Carbon Idioms
Prefer Match Over If-Else Chains
// Less idiomatic
fn Classify(x: i32) -> String {
if (x == 0) {
return "zero";
} else if (x > 0 and x < 10) {
return "small";
} else if (x >= 10) {
return "large";
} else {
return "negative";
}
}
// More idiomatic
fn Classify(x: i32) -> String {
return match (x) {
case 0 => "zero",
case n if n > 0 and n < 10 => "small",
case n if n >= 10 => "large",
default => "negative",
};
}
Use Type System for Validation
// Less idiomatic - runtime validation everywhere
fn ProcessAge(age: i32) {
if (age < 0 or age > 150) {
Print("Invalid age");
return;
}
// use age
}
// More idiomatic - validated type
class Age {
var value: i32;
fn Create(v: i32) -> Result(Self, String) {
if (v < 0 or v > 150) {
return Result.Err("Invalid age");
}
return Result.Ok(Age{.value = v});
}
}
fn ProcessAge(age: Age) {
// age is guaranteed valid
Print("Age: {0}", age.value);
}
Make Invalid States Unrepresentable
// Less idiomatic - multiple booleans
class Connection {
var is_connected: bool;
var is_authenticated: bool;
var is_encrypted: bool;
// Can be in invalid states!
}
// More idiomatic - state machine with variants
variant ConnectionState {
Disconnected,
Connected,
Authenticated,
Encrypted
}
class Connection {
var state: ConnectionState;
fn Connect[self: Self*] -> Result((), String) {
match (self->state) {
case ConnectionState.Disconnected => {
self->state = ConnectionState.Connected;
return Result.Ok(());
}
default => {
return Result.Err("Already connected");
}
}
}
}
Prefer Immutability
// Less idiomatic
fn ProcessData(data: [i32]*) {
for (var i: i32 = 0; i < data->size(); i = i + 1) {
data[i] = data[i] * 2; // Mutation
}
}
// More idiomatic
fn ProcessData(data: [i32]) -> [i32] {
var result: [i32] = [];
for (var i: i32 = 0; i < data.size(); i = i + 1) {
result.push_back(data[i] * 2);
}
return result;
}
Use Interfaces for Abstraction
// Less idiomatic - concrete types everywhere
fn PrintCircle(c: Circle) {
Print("Area: {0}", c.Area());
}
fn PrintRectangle(r: Rectangle) {
Print("Area: {0}", r.Area());
}
// More idiomatic - interface abstraction
interface Shape {
fn Area[self: Self] -> f64;
}
fn PrintShape[T:! Shape](s: T) {
Print("Area: {0}", s.Area());
}
Troubleshooting
Compilation Errors
Uninitialized Variable:
// Error
var x: i32;
Print(x); // Error: use of uninitialized variable
// Fix
var x: i32 = 0;
Print(x);
Type Mismatch:
// Error
var x: i32 = 42;
var y: f64 = x; // Error: type mismatch
// Fix
var x: i32 = 42;
var y: f64 = Convert.ToF64(x);
Missing Interface Implementation:
// Error
fn UseComparable[T:! Comparable](x: T) { }
class MyType { }
fn Main() -> i32 {
var obj: MyType = MyType{};
UseComparable(obj); // Error: MyType doesn't implement Comparable
return 0;
}
// Fix
impl MyType as Comparable {
fn Compare[self: Self](other: Self) -> i32 {
// Implementation
return 0;
}
}
C++ Interop Issues
Header Not Found:
// Error
import Cpp library "myheader.h"; // Error: file not found
// Fix - check include paths in BUILD file
carbon_library(
name = "mylib",
srcs = ["mylib.carbon"],
copts = ["-I/path/to/headers"],
)
Type Incompatibility:
// Error - C++ type not directly usable
fn UseCppVector(vec: Cpp.std.vector(i32)) {
vec[0] = 42; // Might fail
}
// Fix - use C++ API properly
fn UseCppVector(vec: Cpp.std.vector(i32)*) {
vec->at(0) = 42; // Use C++ methods
}
Calling Convention Mismatch:
// Error - wrong calling convention for C++ function
fn CallCppFunction() {
Cpp.MyFunction(); // Might fail if signature wrong
}
// Fix - match C++ signature exactly
import Cpp library "mylib.h";
fn CallCppFunction() {
var result: i32 = Cpp.MyFunction(42, "hello");
}
Build System Issues
Missing Dependency:
# Error in BUILD file
carbon_binary(
name = "myapp",
srcs = ["main.carbon"],
# Missing dependency
)
# Fix
carbon_binary(
name = "myapp",
srcs = ["main.carbon"],
deps = [
":mylib",
"//other:dependency",
],
)
Circular Dependency:
# Error - circular deps
carbon_library(
name = "a",
srcs = ["a.carbon"],
deps = [":b"],
)
carbon_library(
name = "b",
srcs = ["b.carbon"],
deps = [":a"], # Circular!
)
# Fix - refactor to break cycle
carbon_library(
name = "common",
srcs = ["common.carbon"],
)
carbon_library(
name = "a",
srcs = ["a.carbon"],
deps = [":common"],
)
carbon_library(
name = "b",
srcs = ["b.carbon"],
deps = [":common"],
)
Memory Safety Issues
Use After Move:
// Error
var x: MyClass = MyClass.Create();
var y: MyClass = x; // x moved to y
x.Method(); // Error: use after move
// Fix - clone or use references
var x: MyClass = MyClass.Create();
var y: MyClass* = &x; // Borrow, don't move
x.Method(); // OK
Out of Bounds Access:
// Error in debug build
var array: [i32; 5] = [1, 2, 3, 4, 5];
var value: i32 = array[10]; // Panic in debug mode
// Fix - use safe access
fn SafeGet(array: [i32], index: i32) -> Optional(i32) {
if (index >= 0 and index < array.size()) {
return Optional.Some(array[index]);
}
return Optional.None;
}
Generic Type Issues
Constraint Not Satisfied:
// Error
fn RequiresComparable[T:! Comparable](x: T) { }
fn Main() -> i32 {
var s: String = "hello";
RequiresComparable(s); // Error: String doesn't implement Comparable
return 0;
}
// Fix - implement interface
impl String as Comparable {
fn Compare[self: Self](other: Self) -> i32 {
// Implementation
return 0;
}
}
Type Inference Failure:
// Error - can't infer type
var x: auto = SomeGenericFunction(); // Ambiguous
// Fix - specify type explicitly
var x: i32 = SomeGenericFunction();
// Or provide type parameter
var x: auto = SomeGenericFunction[i32]();
Best Practices
- Use checked generics by default, template generics only for C++ interop
- Make error handling explicit with Result/Optional types
- Prefer immutability unless mutation is clearly needed
- Use pattern matching instead of if-else chains
- Leverage the type system to prevent invalid states
- Follow RAII principles for resource management
- Start with C++ interop, gradually refactor to Carbon idioms
- Test extensively during migration from C++
- Use interfaces to define contracts and enable polymorphism
- Document safety assumptions especially around C++ interop
Zero and Default Values
Uninitialized Variables
// Variables must be initialized before use
var x: i32; // Declared but uninitialized
// Print(x); // Error: use of uninitialized variable
x = 42;
Print(x); // OK: now initialized
// Conditional initialization - all paths must initialize
var y: i32;
if (condition) {
y = 10;
} else {
y = 20;
}
Print(y); // OK: initialized in all paths
Default/Zero Values by Type
| Type | Zero Value | Initialization |
|---|---|---|
i8, i16, i32, i64 |
0 |
var x: i32 = 0; |
u8, u16, u32, u64 |
0 |
var x: u32 = 0; |
f32, f64 |
0.0 |
var x: f64 = 0.0; |
bool |
false |
var x: bool = false; |
String |
"" |
var x: String = ""; |
| Array | Empty | var x: [i32] = []; |
| Optional | None |
Optional.None |
| Pointer | null (unsafe) | Use Optional instead |
Struct/Class Initialization
class Config {
var host: String;
var port: i32;
var debug: bool;
}
// All fields must be initialized
var config: Config = Config{
.host = "localhost",
.port = 8080,
.debug = false
};
// Factory function for defaults
class Config {
fn Default() -> Self {
return Config{
.host = "localhost",
.port = 8080,
.debug = false
};
}
}
var config: Config = Config.Default();
Optional for Nullable Values
// Carbon discourages null pointers
// Use Optional instead
fn FindUser(id: i32) -> Optional(User) {
if (UserExists(id)) {
return Optional.Some(GetUser(id));
}
return Optional.None;
}
// Handle optionals
fn GetUsername(id: i32) -> String {
match (FindUser(id)) {
case Optional.Some(user) => {
return user.name;
}
case Optional.None => {
return "Unknown";
}
}
}
// Default value pattern
fn GetOrDefault[T:! Type](opt: Optional(T), default: T) -> T {
return match (opt) {
case Optional.Some(value) => value,
case Optional.None => default,
};
}
Concurrency
Carbon's concurrency model is still under active design. The current focus is on memory safety and C++ interop before finalizing concurrency features.
Current Status (2025)
// Note: Concurrency syntax is subject to change
// This represents expected patterns based on design documents
// Thread safety through ownership
// Carbon aims to prevent data races at compile time
// Similar to Rust's Send/Sync traits
class ThreadSafeCounter {
var count: i32;
fn Increment[self: Self*] {
// Atomic or synchronized access (design pending)
self->count = self->count + 1;
}
}
Expected Concurrency Patterns
// Async/await (expected in future versions)
async fn FetchData(url: String) -> Result(Data, Error) {
var response: Response = await Http.Get(url);
return response.Body();
}
// Structured concurrency (expected)
fn ProcessConcurrently(items: [Item]) -> [Result] {
return parallel for (item in items) {
ProcessItem(item)
};
}
C++ Interop for Concurrency
// For now, use C++ threading via interop
import Cpp library "thread";
import Cpp library "mutex";
fn UseCppThreading() {
var mutex: Cpp.std.mutex = Cpp.std.mutex();
// Lock guard pattern
var lock: Cpp.std.lock_guard(Cpp.std.mutex) =
Cpp.std.lock_guard(&mutex);
// Critical section
DoWork();
}
// Dispatch to thread pool
fn RunInBackground(task: fn()) {
var thread: Cpp.std.thread = Cpp.std.thread(task);
thread.detach();
}
Safety Philosophy
// Carbon's concurrency will emphasize:
// 1. Compile-time data race prevention
// 2. Ownership-based thread safety
// 3. Explicit sharing markers
// 4. Integration with memory safety model
// Expected patterns:
// - Values are "thread-local" by default
// - Explicit markers for shared state
// - Channels for communication (like Go/Rust)
// - Actor model support (planned)
See patterns-concurrency-dev for cross-language concurrency patterns.
Metaprogramming
Carbon provides compile-time metaprogramming through generics and template generics, but does NOT support runtime reflection.
Checked Generics
// Checked generics with interfaces
interface Printable {
fn Print[self: Self]();
}
fn PrintAll[T:! Printable](items: [T]) {
for (var i: i32 = 0; i < items.size(); i = i + 1) {
items[i].Print();
}
}
// Errors caught at definition site
fn BrokenGeneric[T:! Type](x: T) -> T {
// Error: Type doesn't guarantee '+' operator
// return x + x;
}
Template Generics
// Template gener
…(truncated)