Давайте просто посмотрим на структуру кода
import Console;
// Prints the Fibonacci numbers less than `limit`.
fn Fibonacci(limit: i64) {
var (a: i64, b: i64) = (0, 1);
while (a < limit) {
Console.Print(a, " ");
let next: i64 = a + b;
a = b;
b = next;
}
Console.Print("\n");
}
Carbon — это язык, который должен быть знаком разработчикам C++ и C.
Функция, которая мне очень нравится:
Весь исходный код представляет собой текст в кодировке UTF-8. Комментарии, идентификаторы и строки могут содержать символы, отличные от ASCII.
var résultat: String = “Succès”;
строка комментария
// Compute an approximation of π
Примитивные типы
Bool
- логический тип с двумя возможными значениями:True
иFalse
.Int
иUInt
- знаковые и беззнаковые 64-битные целочисленные типы.- Доступны стандартные размеры, как со знаком, так и без знака, включая
i8
,i16
,i32
,i64
,i128
иi256
. - Переполнение в любом направлении является ошибкой.
Float64
— тип с плавающей запятой с семантикой на основе IEEE-754.- Доступны стандартные размеры, включая
f16
,f32
иf128
. BFloat16
также предоставляется.String
— последовательность байтов, рассматриваемая как содержащая текст в кодировке UTF-8.StringView
— доступная только для чтения ссылка на последовательность байтов, рассматриваемую как содержащую текст в кодировке UTF-8.
Кортежи
Кортеж — это набор значений фиксированного размера, которые могут иметь разные типы, где каждое значение идентифицируется по его положению в кортеже. Примером использования кортежей является возврат нескольких значений из функции:
fn DoubleBoth(x: i32, y: i32) -> (i32, i32) {
return (2 * x, 2 * y);
}
- Тип возвращаемого значения представляет собой кортеж из двух типов
i32
. - Выражение использует синтаксис кортежа для построения кортежа из двух значений
i32
.
Типы структур
У углерода также есть структурные типы, члены которых идентифицируются по имени, а не по положению. Они называются структурными классами данных, также известными как структурные типы или структуры.
И типы структур, и значения записываются внутри фигурных скобок ({
...}
). В обоих случаях у них есть список элементов, разделенных запятыми, который начинается с точки (.
), за которой следует имя поля.
- В типе структуры за именем поля следует двоеточие (
:
) и тип, например:{.name: String, .count: i32}
. - В значении структуры, называемом литералом класса структурных данных или литералом структуры, за именем поля следует знак равенства (
=
) и значение, как в{.key = "Joe", .count = 3}
.
Типы указателей
Тип указателей на значения типа
T
записывается какT*
. Углеродные указатели не поддерживают арифметику указателей; единственным указателем операции являются:
- Разыменование: учитывая указатель
p
,*p
дает значениеp
указывает на l-значение.p->m
— это синтаксический сахар для(*p).m
. - Address-of: учитывая l-значение
x
,&x
возвращает указатель наx
.
Массивы и срезы
Тип массива, содержащего 4 значения
i32
, записывается как[i32; 4]
. Существует неявное преобразование кортежей в массивы той же длины, если каждый компонент кортежа может быть неявно преобразован в целевой тип элемента. В случаях, когда размер массива может быть выведен, его можно опустить, например:
var i: i32 = 1; // `[i32;]` equivalent to `[i32; 3]` here. var a: [i32;] = (i, i, i);
Выражения
Выражения описывают некоторое вычисляемое значение. Простейшим примером может быть буквальное число, такое как
42
: выражение, которое вычисляет целочисленное значение 42.
Выражения — это части синтаксиса Carbon, которые производят значения. Поскольку типы в Carbon являются значениями, это включает в себя все, что указано в типе.
fn Foo(a: i32*) -> i32 {
return *a;
}
Здесь тип параметра i32*
, тип возвращаемого значения i32
и операнд *a
оператора return
— все это выражения.
Объявления, определения и области действия
Объявления привязки имени:
Существует два типа объявлений привязки имен:
- объявления констант, представленные с помощью
let
, и - объявления переменных, введенные с помощью
var
.
Для них нет предварительных объявлений; все объявления привязки имен являются определениями.
Объявления констант let
:
Объявление
let
сопоставляет неопровержимый шаблон со значением. В этом примере имяx
связано со значением42
с типомi64
:
let x: i64 = 42;
Здесь x: i64
— это шаблон, за которым следует знак равенства (=
) и значение для сопоставления, 42
. Имена из шаблонов привязки вводятся в объемлющую область.
Объявления переменных var
:
Объявление
var
похоже, за исключением привязокvar
, поэтомуx
здесь является l-значением с хранилищем и адресом, поэтому его можно изменить:
var x: i64 = 42;
x = 7;
Переменные с типом, имеющим несформированное состояние, не нужно инициализировать в объявлении переменной, но их нужно присваивать перед использованием.
auto
Если
auto
используется в качестве типа в объявленииvar
илиlet
, тип является обязательным статическим типом выражения инициализатора.
Функции
Базовое определение функции может выглядеть так:
fn Add(a: i64, b: i64) -> i64 {
return a + b;
}
Это объявляет функцию с именем Add
, которая принимает два параметра i64
, первый из которых называется a
, а второй — b
, и возвращает результат i64
. Он возвращает результат сложения двух аргументов.
С++ может объявить то же самое:
std::int64_t Add(std::int64_t a, std::int64_t b) { return a + b; } // Or with trailing return type syntax: auto Add(std::int64_t a, std::int64_t b) -> std::int64_t { return a + b; }
Пункт возврата:
Предложение return функции указывает тип возвращаемого значения с использованием одного из трех возможных синтаксисов:
->
, за которым следует выражение, напримерi64
, прямо указывает тип возвращаемого значения. Это выражение будет оцениваться во время компиляции, поэтому оно должно быть допустимым в этом контексте.- Например,
fn ToString(val: i64) -> String;
имеет возвращаемый типString
. ->
, за которым следует ключевое словоauto
, указывает, что для определения возвращаемого типа следует использовать вывод типа.- Например,
fn Echo(val: i64) -> auto { return val; }
будет иметь возвращаемый типi64
посредством вывода типа. - Объявления должны иметь известный тип возвращаемого значения, поэтому
auto
недействителен. - Функция должна иметь ровно один оператор
return
. Выражение этого оператораreturn
затем будет использоваться для вывода типа. - Пропуск указывает, что тип возвращаемого значения — пустой кортеж
()
. - Например,
fn Sleep(seconds: i64);
похоже наfn Sleep(seconds: i64) -> ();
. ()
похож на возвращаемый типvoid
в C++.
return
заявлений:
Оператор return
необходим для управления потоком функций. Он завершает поток функции и возвращает выполнение вызывающей стороне.
Когда предложение return опущено, оператор return
не имеет аргумента-выражения, и поток управления функцией неявно завершается после последнего оператора в теле функции, как если бы присутствовал return;
.
Когда предоставляется предложение return, в том числе когда оно равно -> ()
, оператор return
должен иметь выражение, преобразуемое в тип возвращаемого значения, а оператор return
должен использоваться для завершения потока управления функцией.
Объявления функций:
Функции можно объявлять отдельно от определения, предоставляя только подпись без тела. Это предоставляет API, который может быть вызван. Например:
// Declaration: fn Add(a: i64, b: i64) -> i64;
// Definition: fn Add(a: i64, b: i64) -> i64 { return a + b; }
Соответствующее определение может быть предоставлено позже в том же файле или, когда объявление находится в api
файле библиотеки, в impl
файле той же библиотеки. Подпись объявления функции должна соответствовать соответствующему определению. Это включает в себя оговорку о возврате; даже если пропущенный возвращаемый тип имеет поведение, эквивалентное -> ()
, наличие или отсутствие должны совпадать.
Вызовы функций:
Вызовы функций используют идентификатор функции для передачи нескольких аргументов выражения, соответствующих параметрам сигнатуры функции. Например:
fn Add(a: i64, b: i64) -> i64 { return a + b; }
fn Run() { Add(1, 2); }
Здесь Add(1, 2)
— это выражение вызова функции. Add
относится к идентификатору определения функции. Аргументы в скобках 1
и 2
передаются параметрам a
и b
функции Add
.
Параметры
Привязки в списке параметров по умолчанию имеют значение
let
привязки, поэтому имена параметров обрабатываются как r-значения. Это подходит для входных параметров. Эта привязка будет реализована с использованием указателя, если только копирование не является законным, а копирование дешевле.
Если перед привязкой добавить ключевое слово var
, то аргументы будут скопированы (или перемещены из временного хранилища) в новое хранилище и, таким образом, могут быть изменены в теле функции. Копия гарантирует, что любые мутации не будут видны вызывающей стороне.
Используйте тип параметра указатель для представления параметра ввода/вывода, позволяя функции изменять переменную вызывающей стороны. Это делает возможность этих модификаций видимой: путем получения адреса с помощью &
в вызывающем объекте и разыменования с использованием *
в вызываемом объекте.
Выходы функции должны быть возвращены. Несколько значений могут быть возвращены с использованием типа кортеж или структура.
auto
тип возврата:
Если вместо возвращаемого типа используется
auto
, возвращаемый тип функции выводится из тела функции. Он установлен в общий тип статического типа аргументовreturn
операторов в функции. Это не разрешено в предварительном объявлении.
// Return type is inferred to be `bool`, the type of `a > 0`.
fn Positive(a: i64) -> auto {
return a > 0;
}
Блоки и операторы
блок — это последовательность операторов. Блок определяет область и, как и другие области, заключен в фигурные скобки (
{
...}
). Каждый оператор заканчивается точкой с запятой или блоком. Выражения иvar
иlet
являются допустимыми выражениями.
Тело функции определяется блоком, а некоторые операторы потока управления имеют собственные блоки кода. Они вложены в охватывающую область. Например, вот определение функции с блоком операторов, определяющих тело функции, и вложенным блоком как часть оператора while
:
fn Foo() {
Bar();
while (Baz()) {
Quux();
}
}
Операторы присвоения
Операторы присваивания изменяют значение l-value, описанное в левой части присваивания.
- Назначение:
x = y;
.x
присваивается значениеy
. - Увеличение и уменьшение:
++i;
,--j;
.i
устанавливается наi + 1
,j
устанавливается наj - 1
. - Составное присвоение:
x += y;
,x -= y;
,x *= y;
,x /= y;
,x &= y;
,x |= y;
,x ^= y;
,x <<= y;
,x >>= y;
.x @= y;
эквивалентноx = x @ y;
для каждого оператора@
.
В отличие от C++, эти присваивания являются операторами, а не выражениями, и не возвращают значения.
Поток управления
Блоки операторов обычно выполняются последовательно. Операторы потока управления дают дополнительный контроль над потоком выполнения и над тем, какие операторы выполняются.
Некоторые операторы потока управления включают блоки. Эти блоки всегда будут заключены в фигурные скобки {
...}
.
// Curly braces { ... } are required.
if (condition) {
ExecutedWhenTrue();
} else {
ExecutedWhenFalse();
}
if
и else
if
и else
обеспечивают условное выполнение операторов. Синтаксис:
if (
логическое выражение) {
инструкции}
[
else if (
логическое выражение) {
операторы}
] ...
[
else {
утверждения}
]
Будет выполнена только одна группа операторов:
- Когда первое логическое выражение
if
оценивается как истинное, будут выполняться связанные с ним операторы. - Когда более ранние логические выражения оцениваются как ложные, а логическое выражение
else if
оценивается как истинное, будут выполняться связанные с ним операторы. ... else if ...
эквивалентно... else { if ... }
, но без видимой вложенности фигурных скобок.- Когда все логические выражения оцениваются как ложные, будут выполняться связанные операторы
else
.
Когда логическое выражение оценивается как истинное, никакие последующие логические выражения не оцениваются.
Обратите внимание, что else if
может повторяться.
Например:
if (fruit.IsYellow()) {
Print("Banana!");
} else if (fruit.IsOrange()) {
Print("Orange!");
} else if (fruit.IsGreen()) {
Print("Apple!");
} else {
Print("Vegetable!");
}
fruit.Eat();
Этот код будет:
- Оцените
fruit.IsYellow()
: - Когда
True
, вывестиBanana!
и возобновить выполнение вfruit.Eat()
. - Когда
False
, оценитьfruit.IsOrange()
: - Когда
True
, вывестиOrange!
и возобновить выполнение вfruit.Eat()
. - Когда
False
, оценитьfruit.IsGreen()
: - Когда
True
, вывестиOrange!
и возобновить выполнение вfruit.Eat()
. - Когда
False
, вывестиVegetable!
и возобновить выполнение вfruit.Eat()
.
while
Цикл операторов while
до тех пор, пока переданное выражение возвращает True
. Синтаксис:
while (
логическое выражение) {
инструкции}
Например, это печатает 0
, 1
, 2
, затем Done!
:
var x: Int = 0;
while (x < 3) {
Print(x);
++x;
}
Print("Done!");
for
Операторы for
поддерживают зацикливание на основе диапазона, обычно по контейнерам. Синтаксис:
for (
объявление varin
выражение) {
инструкции}
Например, это печатает все имена в names
:
for (var name: String in names) {
Print(name);
}
PrintNames()
печатает каждое String
в names
List
в порядке итерации.
break
Оператор break
немедленно завершает цикл while
или for
. Выполнение возобновится в конце области действия цикла. Синтаксис:
break;
Например, это обрабатывает шаги до тех пор, пока не будет выполнен шаг вручную (если ни один шаг вручную не будет выполнен, обрабатываются все шаги):
for (var step: Step in steps) {
if (step.IsManual()) {
Print("Reached manual step!");
break;
}
step.Process();
}
continue
Оператор continue
сразу переходит к следующему циклу оператора while
или for
. В while
выполнение продолжается с выражения while
. Синтаксис:
continue;
Например, это печатает все непустые строки файла, используя continue
для пропуска пустых строк:
var f: File = OpenFile(path);
while (!f.EOF()) {
var line: String = f.ReadLine();
if (line.IsEmpty()) {
continue;
}
Print(line);
}
match
match
представляет собой поток управления, аналогичный switch
в C и C++, и отражает аналогичные конструкции в других языках, таких как Swift. За ключевым словом match
следует выражение в круглых скобках, значение которого сопоставляется с объявлениями case
, каждое из которых содержит опровержимый шаблон по порядку. За опровержимым шаблоном может дополнительно следовать выражение if
, которое может использовать имена из привязок в шаблоне.
Выполняется код для первого совпадения case
. Необязательный блок default
может быть помещен после объявлений case
, он будет выполнен, если ни одно из объявлений case
не совпадает.
Пример match
:
fn Bar() -> (i32, (f32, f32));
fn Foo() -> f32 { match (Bar()) { case (42, (x: f32, y: f32)) => { return x - y; } case (p: i32, (x: f32, _: f32)) if (p < 13) => { return p * x; } case (p: i32, _: auto) if (p > 3) => { return p * Pi; } default => { return Pi; } } }