Язык программирования Rust и первые впечатления от него

17 ноября 2014

Rust — это язык программирования общего назначения от компании Mozilla, разрабатываемый с 2010-го года. Разработчики Rust ставят перед собой задачу с одной стороны достичь производительности C/C++ (все же понимают, DSL’ем для разработки какого браузера является Rust?), а с другой — умудриться сделать язык высокоуровневым и безопасным. К окончанию 2014-го года планируется выпустить бета-версию Rust 1.0. Если все пойдет хорошо, нас ждет релиз Rust 1.0, после чего Mozilla обещает перестать ломать обратную совместимость.

В отношении Rust справедливо следующее:

  • Язык является кроссплатформенным, поддерживаются Windows (>= 7, на данный момент только x86), а также Linux и MacOS (x86 и amd64);
  • Компилятор Rust написан на Rust и использует LLVM;
  • Многое было позаимствовано из мира ФП — лямбды, замыкания, кортежи, алгебраические типы данных, паттерн матчинг, fold, map, filter, переменные по умолчанию неизменяемы;
  • Используется строгая статическая типизация с автоматическим выводом типов;
  • В языке есть метапрограммирование (типизированное);
  • Есть генерики, наследования как такового нет, только тайпклассы;
  • Нет неявного преобразования типов, размер примитивных типов как правило не зависит от платформы, нет никакого null;
  • Язык поддерживает Unicode, все строки хранятся в UTF-8 (подобно тому, как это сделано в Vala) вместе с длиной и могут содержать в себе нулевые символы;
  • В стандартной библиотеке есть легковесные потоки (upd: выпилили) и типизированные каналы для взаимодействия между ними, а также футуры;
  • У языка нет полноценного GC, данные размещаются либо в стеке, либо в куче, но память освобождается при выходе из скоупа, либо используются счетчики ссылок (для смелых есть и обычные ссылки);
  • Компилятор очень жестко следит за тем, как вы работаете с памятью, например, если он заподозрит возможность состояния гонки, программа не скомпилируется;
  • При очень сильном желании эти проверки можно обойти, что особенно удобно, например, если вы хотите слинковаться с кодом на Си;
  • Rust имеет некоторый рантайм, но на языке также можно писать и без рантайма, что позволяет использовать Rust в задачах типа разработки ядра ОС;

Идею, как я лично ее понял, можно описать примерно так. Вот у нас есть всякие языки с GC типа Java и Go. И из-за этого GC они тормозят. И есть Си, который не тормозит, но в нем можно наделать кучу ошибок, из которых многие как раз связаны с управлением памятью. А давайте возьмем Си, избавим его от родовых травм (уберем инклуды, добавим генерики, кортежи, алгебраические типы данных и тд), а касательно сборки мусора скажем следующее. Вот у сишников есть определенные паттерны управления памятью (см например RAII). Давайте использовать эти паттерны и напишем такой умный компилятор, который сделает очень сложным написание кода, в котором что-то течет или ссылается в никуда. Получим и скорость и безопасность.

Но хватит теории, перейдем к практике.

На момент написания этих строк самый простой способ установить последнюю версию Rust под Ubuntu Linux заключался в выполнении следующей команды:

curl -s https://static.rust-lang.org/rustup.sh | sudo sh

Традиционный hello world:

fn main() {
  println!("Hello!")
}

Восклицательный знак означает всего лишь, что println в Rust — это макрос (не имеет ничего общего с макросами в Си).

Компилируем, запускаем:

rustc hello.rs
./hello

Теперь рассмотрим пример поинтереснее. Напишем программу, которая на вход принимает список URL, а возвращает HTML-код списка ссылок, в котором в качестве текста ссылок использовались соответствующие title.

Создадим новый проект:

cargo new shownotegen --bin

Откроем файл Cargo.toml и исправим его таким образом:

[package]

name = "shownotegen"
version = "0.0.1"
authors = ["Aleksander Alekseev <mail@eax.me>"]

[[bin]]
name="main"
path="src/main.rs"

[dependencies.http]
git = "https://github.com/chris-morgan/rust-http.git"

В файле src/main.rc напишем:

#![feature(phase)]

extern crate http;
extern crate url;
extern crate regex;

#[phase(plugin)]
extern crate regex_macros;

use http::client::RequestWriter;
use http::method::Get;
use std::{os, str};
use url::{Url, ParseError};
use std::io::{BufferedReader, File, IoError};

#[deriving(Show)]
enum GetTitleError {
  InvalidUrl(ParseError),
  RequestSendFailed(IoError),
  ResponseReadFailed(IoError),
  BodyDecodeFailed,
  ParseFailed
}

fn main() {
  let args = os::args();
  match args.len() {
    0 => unreachable!(),
    2 => {
      let path = Path::new(args[1].as_slice());
      let mut file = BufferedReader::new(File::open(&path));
      for line in file.lines() {
        let tmp = line.unwrap();
        let url = tmp.trim();
        let text = match get_title(url.as_slice()) {
          Ok(title) => title,
          Err(err) => format!("NO TITLE ({})", err).to_string(),
        };
        println!("<li><a href=\"{}\">{}</a></li>", url, text);
      }
    },
    _ => println!("Usage: {} <file>", args[0]),
  };
}

fn get_title(url: &str) -> Result<String, GetTitleError> {
  let body = try!(get_html(url));
  let re = regex!(r"(?is)<title>(.*?)</title>");
  match re.captures(body.as_slice()) {
    Some(cap) => Ok(cap.at(1).to_string()),
    None => Err(ParseFailed),
  }
}

fn get_html(url: &str) -> Result<String, GetTitleError> {
  let url = match Url::parse(url) {
    Ok(u) => u,
    Err(e) => return Err(InvalidUrl(e)),
  };

  let request: RequestWriter = RequestWriter::new(Get, url).unwrap();

  let mut response = match request.read_response() {
    Ok(resp) => resp,
    Err( (_, err) ) => return Err(RequestSendFailed(err)),
  };

  match response.read_to_end() {
    Ok(body) => {
      match str::from_utf8(body.as_slice()) {
        Some(rslt) => Ok(rslt.to_string()),
        None => Err(BodyDecodeFailed),
      }
    },
    Err(err) => Err(ResponseReadFailed(err)),
  }
}

Как вы уже поняли, описание проекта и его зависимостей задается в файле Cargo.toml. Сборка, тестирование, запуск проекта и так далее, производятся при помощи команд вроде cargo build, cargo test и cargo run. В общем, все очень похоже на Rebar из мира Erlang. Попробуйте собрать программу и проверьте, что она работает.

При написании этой заметки я еще хотел рассказать про настройку Vim, но оказалось, что в Ubuntu 14.04 он из коробки умеет подсвечивать синтаксис Rust. Также был обнаружен плагин для IntelliJ IDEA, но сам я его не пробовал. Судя по описанию, в нем реализована только подсветка синтаксиса для .rs файлов.

Мои первые впечатления от Rust следующие. Язык выглядит очень годно. Одно только наличие генериков делают его на голову выше Go. Писать на Rust оказалось на удивление легко и приятно. В этом смысле опыт был похож на опыт программирования на скриптовых языках. Опять же, чего мне не удалось испытать, программируя на Go. При совершении мной глупых ошибок компилятор поправлял меня, а после компиляции код оказывался рабочим с первого раза. В этом смысле Rust напомнил мне Haskell и OCaml.

С другой стороны, меня печалит, что приходится (1) следить за пунктуацией, расставлять запятые и точки с запытой, или (2) помнить о различиях между &str и String и постоянно расставлять to_string() и as_slice(). Возможно, это приходит с опытом. Но вообще-то говоря, мне не хотелось бы думать о таких вещах. Также на момент написания этих строк многие вещи остаются для меня непонятными. В какой именно момент происходит переключение контекста в легковесных процессах? Есть ли жизнь без нормальных исключений и GC? Что, если я хочу сделать двусвязный список? Как не запутаться во всех этих видах указателей, borrowing и так далее?

Одно можно сказать с полной уверенностью. Rust — язык очень интересный и впереди нас ждет не один год экспериментов с Rust, очень странных проектов на нем, а также множество конструктивных обсуждений этого языка в интернетах.

Ссылки по теме:

Дополнение: После публикации поста список ссылок по теме был обновлен. Также вас может заинтересовать заметка Критика языка Rust и почему C/C++ никогда не умрет.

Метки: , .


Вы можете прислать свой комментарий мне на почту, или воспользоваться комментариями в Telegram-группе.