Carbon语法速览!

946 阅读7分钟

7月19日Cpp North大会上,谷歌的C++专家Chandler Carruth发表了『C++: What Comes Next? (Announcing the Carbon Language experiment) 』的主题演讲,官宣了正在实验中的Carbon语言,其目标是成为C++的继任者。 该消息迅速火爆全球,中文互联网圈在7月20号也开始大量报道这一消息。

Chandler Carruth在谷歌负责LLVM编译器的优化,也是谷歌在 C++ 委员会的代表。

目前Carbon语言在Github上已经开源,地址:github.com/carbon-lang…

7月22日,Cpp North的Youtube频道公开了这一演讲的视频:www.youtube.com/watch?v=omr…

接下来让我们来速览一下Carbon的语法,首先声明本文的Carbon代码都是基于Chandler Carruth的Keynote以及当前Carbon的github仓库中的代码。

由于Carbon还在实验阶段,Chandler Carruth的Keynote中演示某些语法,当前的Carbon还不支持编译……,比如abstract class、类外定义函数等等。当然已经实现的语法部分可能不稳定,后期也可能会再有重大调整也未可知。

由于当下并且没有支持Carbon语法的代码高亮插件,所以下面代码中的高亮并不准,敬请见谅。

Hello World

package ExplorerTest api;

第一行是定义了package(包),包名是ExplorerTest。后面的api,那个不是包名的一部分,但是又不能省略(当前可以是api或impl)。

fn Main() -> i32 {
  var s: auto = "Hello world!";
  Print(s);
  return 0;
}

定义了main函数,Carbon中使用的Main()。从这个Hello World的例子中,变量语法、函数定义语法可见一斑。

变量

var

即变量:

var x: i64 = 42;
x = 7;

笔者点评:个人感觉还不如C++的语法简洁…… int64_t x = 42;

冒号后面是变量的类型,i64是int64,也可以使用auto做类型推导:

var x: auto = 42;

let

基本等同于C++中常量,Rust也采用let表示常量。

let x: i64 = 42;

同样也支持auto

let x: auto = 42;

控制流

if else

if (fruit.IsYellow()) {
  Console.Print("Banana!");
} else if (fruit.IsOrange()) {
  Console.Print("Orange!");
} else {
  Console.Print("Vegetable!");
}

和C/C++一样。

while

var x: i32 = 0;
while (x < 3) {
  Console.Print(x);
  ++x;
}
Console.Print("Done!");

也和C++一样

for

for (var step: Step in steps) {
  if (step.IsManual()) {
    Console.Print("Reached manual step!");
    break;
  }
  if (step.NotReady()) {
    continue;
  }
  step.Process();
}

语法是for-range循环的语法。同样也能发现Carbon支持break和continue。

match

Carbon中没有switch,但是有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;
    }
  }
}

不过C++中的switch能比较的只能是整型、枚举。

函数

fn Add(a: i64, b: i64) -> i64 {
  return a + b;
}

Carbon中这个Add函数的写法和Rust中实现一个Add几乎一模一样。 比如声明参数的时候类型在后,并且冒号分割的参数写法。还有fn关键字。

当然在函数体内逻辑复杂的时候,会和Rust不同,因为Rust还有不带分号的表达式语法,表达式的值就是整个函数的值。Carbon没有那种怪异的东西。

另外Carbon也能进行返回值的类型推导:

fn Add(a: i64, b: i64) -> auto {
  return a + b;
}

一个更复杂的函数的例子:

package Geometry api;

import Math; // 导入其他包

class Circle { // 定义一个Circle类
    var r: f32;
}

fn ScaleAreaAndAppend(circle: Circle, log2_scale: i32,
                      results: Vector(f32)*) {
  var area: f32 = Math.Pi * c.r * c.r;
  let scale: i32 = 1 << log2_scale;
  area *= scale;
  results->append(area);
}

参数默认都是常量,除非声明成指针类型。

面向对象

类与对象

class Point {
  var x: i32;
  var y: i32;
}
 
fn Main() -> i32 {
  var p1: Point = {.x = 1, .y = 2}; 
  var p2: auto = p1; 
  p2.x = 3;
  return p1.x - 1;
}

成员变量与成员函数

class NewsAriticle {
  // 类似C++的static
  fn Make(headline: String, body_html: String) -> NewsAritcle();

  // 只读方法
  fn AsHtml[me: Self]() -> String;

  // 可修改方法
  fn Publish[addr me: Self*]() { me->published = DateTime.Now(); }

  private var headline: String;
  private var body_html: String;
  private var published: Optional(Datetime);
}
// 类外定义成员函数
fn NewsAriticle.AsHtml[me: Self]() -> String{ ... }

Carbon中类中成员访问控制权限默认都是public,如果需要声明成私有则需要单独加private关键字。这个行为和C/C++的struct相同,但是和主流语言的class都不同。

定义在类中的函数,如果有[me: Self]表示是只读的成员函数,在函数中不能修改类对象的成员变量。me在函数体中是表示对当前对象的引用,类似C++的(*this)

如果有[addr me: Self*]表示的是可对当前对象进行修改的函数。me在函数体中类似C++的this指针。

[me: Self][addr me: Self*]的成员函数,也可以称作方法(method),如果类中的函数没有[me: Self][addr me: Self*],则表示是一个和对象无关的函数,等价于C++中的static成员函数。这个设计很像python中的类中成员函数的设计。

继承与抽象

Carbon只支持单继承,这没的说。值得注意的是普通的class关键字定义的类型默认都是final的,即不能被继承生成子类(俗称『绝育』)。但abstrct classbase class关键字定义的类型可以被继承:

// 抽象类(abstract class)不能被实例化,因为其中可能包含抽象方法
abstract class UIWidget {
  // 抽象方法(abstract fn)没有实现
  abstract fn Draw[me: Self](s: Screen);
  abstract fn Click[addr me: Self*](x: i32, y: i32);
}

// base class 允许扩展和实例化 
base class Button extends UIWidget {
  // 实现抽象方法
  impl fn Draw[me: Self](s: Screen) { ... }
  impl fn Click[addr me: Self*];

  // 新增了一个虚函数(virtual fn)
  virtual fn MoveTo[addr me: Self*](x: i32, y: i32);
}

// 类外实现方法
fn Button.Click[addr me: Self*](x: i32, y: i32) { ... }
fn Button.MoveTo[addr me: Self*](x: i32, y: i32) { ... }

class ImageButton extends Button {
  ...
}

abstrct class就是抽象类,它不能被实例化,因为其中有抽象方法。抽象类与抽象方法的概念和Java类似。抽象方法等同于C++中的纯虚函数

base class不仅是可以被继承(扩展)的类,还能实例化。因为它里面不会有抽象方法,所有继承而来的抽象方法都要被实现。base class中也能用virtual修饰成员函数,这个语法是从C++中的虚函数而来的。

泛型

泛型接口

定义泛型接口来做泛型代码的类型检查

interface Summary {
  fn Summarize[me: Self]() -> String;
}

这个interface不是Java中的interface,而是有点像C++中的Concept,对泛型做类型检查用的。

泛型函数

fn PrintSummary[T:! Summary](x: T) {
  Console.Print(x.Summarize());
}

定义了一个支持Summary泛型接口的泛型函数PrintSummary

实现泛型接口

class NewsArticle {
  ...
  impl as Summary {
    fn Summarize[me: Self]() -> String { ... }
  }
}

所以可以使用泛型函数来调用实现了泛型接口的类对象

  // n 是 NewsArticle类型
  PrintSummary(n);

也可以直接调用

  // n 是 NewsArticle类型
  n.Summarize();

扩展其他包的API

import OtherPackage;

interface Summary {
  fn Summarize[me: Self]() -> String;
}

// 泛型函数
fn PrintSummary[T:! Summary](x: T) {
  Console.Print(x.Summarize());
}

// 扩展外部API的接口
external impl OtherPackege.Tweet as Summary {
  fn Summarize[me: Self]() -> String { ... }
}

fn SummarizeTweet(t: Tweet) {
  PrintSummary(t);
}

我们导入了一个外部的包OtherPackege,它之中有一个Tweet类型,然后我们可以通过external impl来扩展它支持它本不存在的泛型接口。

指针

基本语法:

  // 定义i32类型变量x,值为5
  var x: i32 = 5;
  // 把x的值改成10
  x = 10;
  // 定义i32*类型的指针p,指向变量x的地址
  var p: i32* = &x;
  // 通过指针修改x的值为7
  *p = 7;
  // 定义i32*类型的指针q,使用&*p,同样指向变量x的地址
  var q: i32* = &*p;
  // 通过指针q修改x的值为0
  *q = 0;
  // 定义一个i32类型的变量y,值为0
  var y: i32 = *p;

另外Carbon的指针不支持空指针,如果想表示不存在,使用Optional。

与C++互操作

与C++互操作是Carbon宣传的重点,也是最大难点。现在Carbon语言还不完善,这里举一个Keynote中演示的例子。

有一个C++的头文件circle.h

struct Circle {
  float r;
}

Carbon调用C++

然后编写一个Carbon代码文件:geometry.carbon

package Geometry api;
import Math;

import Cpp library "circle.h";

fn PrintArea(circles: Slice(Cpp.Circle)) {
  var area: f32 = 0;
  for (c: Cpp.Circle in circles) {
    area += Math.Pi * c.r * c.r;
  }
  Print("Total area: {0}", area);
}

可以通过 import Cpp library "circle.h"; 这种语法来引用C++头文件中声明的类型。

C++调用Carbon

在写一个C++的源文件:

#include <vector>
#include "circle.h"

#include "geometry.carbon.h"

auto main(int argc, char* argv) -> int {
  std::vector<Circle> circles = {{1.0}, {2.0}};

  Geometry::PrintArea(circles);
  return 0;
}

最后提醒

最后再次提醒,某些KeyNote中演示的Carbon语法仅仅是当前规划中的设计,但还没有被实现。当前已经实现,能够通过编译语法,参考Github仓库中explorer/testdata目录中的代码。