Игрушечный фронтенд для LLVM, написанный на Rust: Руководство для начинающих

http://blog.ulysse.io/2016/07/03/llvm-getting-started.html
  • Перевод
  • Tutorial
Примечание переводчика
Приведённый в статье код скомпилирован с достаточно старыми версиями крейтов peg и peg_syntax_ext. Для текущих версий в исходники нужно внести минимальные изменения. Я вставил изменённые участки в спойлеры по тексту статьи. Для сборки кода установите компилятор nightly Rust.
Полный исходник с моими правками можно скачать здесь: https://github.com/arktur04/rust-llvm-toy-frontend


В настоящее время я работаю над компилятором, который написан на Rust, и порождает LLVM IR. LLVM API выглядит немного пугающе для новичков, и по нему не так много руководств (и все они на C++, поэтому не вполне очевидно, как сделать то же самое на Rust). Я бы хотел, чтобы кто-то протянул мне руку помощи, когда я начинал всё это, и эта статья является тем, что я хотел бы показать самому себе в то время.



В Rust наилучшая возможность взаимодействия с LLVM — через крейт llvm-sys. Один добрый человек разместил документацию к нему здесь. Конечно, вам следует также изучить руководство по LLVM, так как оно поможет вам понять, как LLVM “думает”. Этот пост, в основном, является переводом на Rust подмножества из этого руководства.

Полный исходный код для этого руководства находится здесь.

Получаем рабочее окружение для разработки


Для начинающих, вот способ, чтобы начать работать с LLVM:

# `curl` is just so we can next install Rust
sudo apt-get -y install clang curl llvm-3.8-dev
curl https://sh.rustup.rs -sSf | sh

# The `llvm-sys` crate expects something called `llvm-config` on your PATH.
sudo ln -s /usr/bin/llvm-config-3.8 /usr/bin/llvm-config

Если вы работаете на свежей Ubuntu (возможно, вам понадобится apt-get update), то всё готово, можно начинать. Если нет, вы можете запуститься в виртуальной машине Vagrant, через Vagrantfile:

Vagrant.configure("2") do |config|
  config.vm.box = "bento/ubuntu-16.04"
end

Вы можете приступить, запустив cargo init llvm-example --bin и поместив следующее(взятое из llvm-sys) в src/main.rs:

//! Construct a function that does nothing in LLVM IR.
extern crate llvm_sys as llvm;

use std::ptr;

fn main() {
    unsafe {
        // Set up a context, module and builder in that context.
        let context = llvm::core::LLVMContextCreate();
        let module = llvm::core::LLVMModuleCreateWithName(b"nop\0".as_ptr() as *const _);
        let builder = llvm::core::LLVMCreateBuilderInContext(context);

        // Get the type signature for void nop(void);
        // Then create it in our module.
        let void = llvm::core::LLVMVoidTypeInContext(context);
        let function_type = llvm::core::LLVMFunctionType(void, ptr::null_mut(), 0, 0);
        let function = llvm::core::LLVMAddFunction(module, b"nop\0".as_ptr() as *const _,
                                                   function_type);

        // Create a basic block in the function and set our builder to generate
        // code in it.
        let bb = llvm::core::LLVMAppendBasicBlockInContext(context, function,
                                                           b"entry\0".as_ptr() as *const _);
        llvm::core::LLVMPositionBuilderAtEnd(builder, bb);

        // Emit a `ret void` into the function
        llvm::core::LLVMBuildRetVoid(builder);

        // Dump the module as IR to stdout.
        llvm::core::LLVMDumpModule(module);

        // Clean up. Values created in the context mostly get cleaned up there.
        llvm::core::LLVMDisposeBuilder(builder);
        llvm::core::LLVMDisposeModule(module);
        llvm::core::LLVMContextDispose(context);
    }
}

И в вашем Cargo.toml:

[package]
name = "llvm-example"
version = "0.1.0"
authors = ["Ulysse Carion <ulysse@ulysse.io>"]

[[bin]]
name = "main"

[dependencies]
llvm-sys = "0.2"

Вы должны получить следующее:

vagrant@vagrant:/vagrant$ cargo run
   Compiling llvm-example v0.1.0 (file:///vagrant)
     Running `target/debug/main`
; ModuleID = 'nop'

define void @nop() {
entry:
  ret void
}

Ура! Мы можем начинать писать свою собственную программу.

Немного менее тривиальная программа


Для начала, скомпилируем программу, которая просто возвращает код завершения путём возврата целого из функции main.

Вот как я это сделал: (Нам скоро понадобится парсер, и я добавил его сейчас; я использовал крейт peg):

Прим. перев.
Текст Cargo.toml:


[package]
name = "llvm-example"
version = "0.1.0"
authors = ["Ulysse Carion <ulysse@ulysse.io>"]

[[bin]]
name = "main"

[dependencies]
llvm-sys = "38"
peg = "0.5.4"
peg-syntax-ext = "0.5.2"


#![feature(plugin)]
#![plugin(peg_syntax_ext)]

extern crate llvm_sys as llvm;

use std::ffi::CString;
use std::fs::File;
use std::io::Read;
use std::ptr;

fn main() {
    let mut input = String::new();
    let mut f = File::open("in.ex").unwrap();
    f.read_to_string(&mut input).unwrap();

    let parsed_input = parser::program(&input).unwrap();

    unsafe {
        codegen(parsed_input);
    }
}

peg! parser(r#"
    #[pub]
    program -> String
        = i:int_literal "\n" { i }

    int_literal -> String
        = [0-9]+ { match_str.to_owned() }
"#);

unsafe fn codegen(input: String) {
    let context = llvm::core::LLVMContextCreate();
    let module = llvm::core::LLVMModuleCreateWithName(b"example_module\0".as_ptr() as *const _);
    let builder = llvm::core::LLVMCreateBuilderInContext(context);

    // In LLVM, you get your types from functions.
    let int_type = llvm::core::LLVMInt64TypeInContext(context);
    let function_type = llvm::core::LLVMFunctionType(int_type, ptr::null_mut(), 0, 0);
    let function = llvm::core::LLVMAddFunction(module, b"main\0".as_ptr() as *const _, function_type);

    let entry_name = CString::new("entry").unwrap();
    let bb = llvm::core::LLVMAppendBasicBlockInContext(context, function, entry_name.as_ptr());
    llvm::core::LLVMPositionBuilderAtEnd(builder, bb);

    // The juicy part: construct a `LLVMValue` from a Rust value:
    let int_value: u64 = input.parse().unwrap();
    let int_value = llvm::core::LLVMConstInt(int_type, int_value, 0);

    llvm::core::LLVMBuildRet(builder, int_value);

    // Instead of dumping to stdout, let's write out the IR to `out.ll`
    let out_file = CString::new("out.ll").unwrap();
    llvm::core::LLVMPrintModuleToFile(module, out_file.as_ptr(), ptr::null_mut());

    llvm::core::LLVMDisposeBuilder(builder);
    llvm::core::LLVMDisposeModule(module);
    llvm::core::LLVMContextDispose(context);
}

Прим. перев.
Изменения в парсере:

peg! parser(r#"
    #[pub]
    program -> String
        = i:int_literal "\n" { i }

    int_literal -> String
        = n:$([0-9]+) { n.to_owned() }
"#);


Работает! Проверяем:

vagrant@vagrant:/vagrant$ cat in.ex
42
vagrant@vagrant:/vagrant$ cargo run
     Running `target/debug/main`
vagrant@vagrant:/vagrant$ lli-3.8 out.ll ; echo $?
42

Круто! Вот так выглядит out.ll:


; ModuleID = 'example_module'

define i64 @main() {
entry:
  ret i64 42
}

Арифметика


Добавим поддержку сложения, вычитания, умножения, и деления чисел. Чтобы сделать это, нам нужно расширить нашу грамматику. Давайте введём enum, который представляет AST:

pub enum Expr {
    Add(Box<Expr>, Box<Expr>),
    Sub(Box<Expr>, Box<Expr>),
    Mul(Box<Expr>, Box<Expr>),
    Div(Box<Expr>, Box<Expr>),
    Literal(String),
}

Также нам нужно расширить грамматику:

// `product` and `sum` are that way to get operator precedence
peg! parser(r#"
    use super::Expr;

    #[pub]
    program -> Expr
        = e:expression "\n" { e }

    expression -> Expr
        = sum

    sum -> Expr
        = a:product _ "+" _ b:sum { Expr::Add(Box::new(a), Box::new(b)) }
        / a:product _ "-" _ b:sum { Expr::Sub(Box::new(a), Box::new(b)) }
        / product

    product -> Expr
        = a:int_literal _ "*" _ b:product { Expr::Mul(Box::new(a), Box::new(b)) }
        / a:int_literal _ "/" _ b:product { Expr::Div(Box::new(a), Box::new(b)) }
        / int_literal

    int_literal -> Expr
        = [0-9]+ { Expr::Literal(match_str.to_owned()) }

    _ = " "*
"#);

Прим. перев.
Изменения в парсере:

// `product` and `sum` are that way to get operator precedence
peg! parser(r#"
    use super::Expr;

    #[pub]
    program -> Expr
        = e:expression "\n" { e }

    expression -> Expr
        = sum

    sum -> Expr
        = a:product _ "+" _ b:sum { Expr::Add(Box::new(a), Box::new(b)) }
        / a:product _ "-" _ b:sum { Expr::Sub(Box::new(a), Box::new(b)) }
        / product

    product -> Expr
        = a:int_literal _ "*" _ b:product { Expr::Mul(Box::new(a), Box::new(b)) }
        / a:int_literal _ "/" _ b:product { Expr::Div(Box::new(a), Box::new(b)) }
        / int_literal

    int_literal -> Expr
        = n:$([0-9]+) { Expr::Literal(n.to_owned()) }

    _ = " "*
"#);


Затем мы генерируем код. Вы можете определять строки, такие как “addtmp”, и они будут использоваться как часть имени соответствующего регистра в IR.

// When you write out instructions in LLVM, you get back `LLVMValueRef`s. You
// can then use these references in other instructions.
unsafe fn codegen_expr(context: LLVMContextRef, builder: LLVMBuilderRef, expr: Expr) -> LLVMValueRef {
    match expr {
        Expr::Literal(int_literal) => {
            let int_type = llvm::core::LLVMInt64TypeInContext(context);
            llvm::core::LLVMConstInt(int_type, int_literal.parse().unwrap(), 0)
        },

        Expr::Add(lhs, rhs) => {
            let lhs = codegen_expr(context, builder, *lhs);
            let rhs = codegen_expr(context, builder, *rhs);

            let name = CString::new("addtmp").unwrap();
            llvm::core::LLVMBuildAdd(builder, lhs, rhs, name.as_ptr())
        },

        Expr::Sub(lhs, rhs) => {
            let lhs = codegen_expr(context, builder, *lhs);
            let rhs = codegen_expr(context, builder, *rhs);

            let name = CString::new("subtmp").unwrap();
            llvm::core::LLVMBuildSub(builder, lhs, rhs, name.as_ptr())
        },

        Expr::Mul(lhs, rhs) => {
            let lhs = codegen_expr(context, builder, *lhs);
            let rhs = codegen_expr(context, builder, *rhs);

            let name = CString::new("multmp").unwrap();
            llvm::core::LLVMBuildMul(builder, lhs, rhs, name.as_ptr())
        },

        Expr::Div(lhs, rhs) => {
            let lhs = codegen_expr(context, builder, *lhs);
            let rhs = codegen_expr(context, builder, *rhs);

            let name = CString::new("divtmp").unwrap();
            llvm::core::LLVMBuildUDiv(builder, lhs, rhs, name.as_ptr())
        },
    }
}

Сейчас вы можете исполнять программы типа 10 * 4 + 20 / 2 — 8! Очень круто, как мне кажется.

Переменные


Мы пойдём по простому пути и допустим, что наша программа не делает разных раздражающих вещей, таких как ссылки на неопределённые переменные. Мы просто сохраняем переменные в регистры, и сохраняем их в HashMap<String, LLVMValueRef>. Это работает, потому что программа имеет только один путь исполнения.
Расширяем язык и парсер:

pub enum Expr {
    Literal(String),
    Ref(String),
    Assign(String, Box<Expr>),
    Add(Box<Expr>, Box<Expr>),
    Sub(Box<Expr>, Box<Expr>),
    Mul(Box<Expr>, Box<Expr>),
    Div(Box<Expr>, Box<Expr>),
}

peg! parser(r#"
    use super::Expr;

    #[pub]
    program -> Vec<Expr>
        = e:(expression ** "\n") "\n" { e }

    expression -> Expr
        = i:identifier _ "=" _ s:sum { Expr::Assign(i, Box::new(s)) }
        / sum

    sum -> Expr
        = a:product _ "+" _ b:sum { Expr::Add(Box::new(a), Box::new(b)) }
        / a:product _ "-" _ b:sum { Expr::Sub(Box::new(a), Box::new(b)) }
        / product

    product -> Expr
        = a:ref_or_literal _ "*" _ b:product { Expr::Mul(Box::new(a), Box::new(b)) }
        / a:ref_or_literal _ "/" _ b:product { Expr::Div(Box::new(a), Box::new(b)) }
        / ref_or_literal

    ref_or_literal -> Expr
        = i:identifier { Expr::Ref(i) }
        / int_literal

    identifier -> String
        = [a-zA-Z]+ { match_str.to_owned() }

    int_literal -> Expr
        = [0-9]+ { Expr::Literal(match_str.to_owned()) }

    _ = " "*
"#);

Прим. перев.
Изменения в парсере:


peg! parser(r#"
    use super::Expr;

    #[pub]
    program -> Vec<Expr>
        = e:(expression ** "\n") "\n" { e }

    expression -> Expr
        = i:identifier _ "=" _ s:sum { Expr::Assign(i, Box::new(s)) }
        / sum

    sum -> Expr
        = a:product _ "+" _ b:sum { Expr::Add(Box::new(a), Box::new(b)) }
        / a:product _ "-" _ b:sum { Expr::Sub(Box::new(a), Box::new(b)) }
        / product

    product -> Expr
        = a:ref_or_literal _ "*" _ b:product { Expr::Mul(Box::new(a), Box::new(b)) }
        / a:ref_or_literal _ "/" _ b:product { Expr::Div(Box::new(a), Box::new(b)) }
        / ref_or_literal

    ref_or_literal -> Expr
        = i:identifier { Expr::Ref(i) }
        / int_literal

    identifier -> String
        = n:$([a-zA-Z]+) { n.to_owned() }

    int_literal -> Expr
        = n:$([0-9]+) { Expr::Literal(n.to_owned()) }

    _ = " "*
"#);


Затем добавляем поддержку для двух новых выражений:

unsafe fn codegen_expr(context: LLVMContextRef, builder: LLVMBuilderRef, names: &mut HashMap<String, LLVMValueRef>, expr: Expr) -> LLVMValueRef {
    match expr {
        // ...

        Expr::Ref(name) => {
            *names.get(&name).unwrap()
        },

        Expr::Assign(name, expr) => {
            let new_value = codegen_expr(context, builder, names, *expr);
            names.insert(name, new_value);
            new_value
        },
    }
}

И немного изменяем функцию codegen:

let int_type = llvm::core::LLVMInt64TypeInContext(context);
let zero = llvm::core::LLVMConstInt(int_type, 0, 0);

let mut names = HashMap::new();
let mut return_value = zero; // return value on empty program
for expr in input {
    return_value = codegen_expr(context, builder, &mut names, expr);
}
llvm::core::LLVMBuildRet(builder, return_value);

Вуаля! Проверяем:

vagrant@vagrant:/vagrant$ cat in.ex
a = 3
b = 76
a + b
vagrant@vagrant:/vagrant$ cargo run
     Running `target/debug/main`
vagrant@vagrant:/vagrant$ cat out.ll
; ModuleID = 'example_module'

define i64 @main() {
entry:
  ret i64 79
}

If


С if всё становится несколько сложнее. Самый простой путь заставить его работать, это сохранять локальные переменные в стеке, и позволить LLVM произвести оптимизацию. В LLVM, вы создаёте стековую переменную командой alloca, и затем читаете/пишете командами load/store.

Для того, чтобы сделать это, мы опять расширяем язык и грамматику, добавляя новые правила парсера:

expression -> Expr
    = if_expression
    / i:identifier _ "=" _ s:expression { Expr::Assign(i, Box::new(s)) }
    / sum

if_expression -> Expr
    = "if" _ e:expression _ "{\n" _ then_body:statements _ "}" _ "else" _ "{\n" _ else_body:statements _ "}" {
        Expr::If(Box::new(e), then_body, else_body)
    }

И добавляем новый тип узла AST:

pub enum Expr {
    Literal(String),
    Ref(String),
    Assign(String, Box<Expr>),
    Add(Box<Expr>, Box<Expr>),
    Sub(Box<Expr>, Box<Expr>),
    Mul(Box<Expr>, Box<Expr>),
    Div(Box<Expr>, Box<Expr>),
    If(Box<Expr>, Vec<Expr>, Vec<Expr>),
}

Наконец, генерируем код для выражения if:

unsafe fn codegen_expr(context: LLVMContextRef, builder: LLVMBuilderRef, func: LLVMValueRef, names: &mut HashMap<String, LLVMValueRef>, expr: Expr) -> LLVMValueRef {
    match expr {
        // ...

        Expr::If(condition, then_body, else_body) => {
            let condition_value = codegen_expr(context, builder, func, names, *condition);
            let int_type = llvm::core::LLVMInt64TypeInContext(context);
            let zero = llvm::core::LLVMConstInt(int_type, 0, 0);

            // `is_nonzero` is a 1-bit integer
            let name = CString::new("is_nonzero").unwrap();
            let is_nonzero = llvm::core::LLVMBuildICmp(builder, llvm::LLVMIntPredicate::LLVMIntNE, condition_value, zero, name.as_ptr());

            // It's fine to create blocks first, and then fill them in later.
            let entry_name = CString::new("entry").unwrap();
            let then_block = llvm::core::LLVMAppendBasicBlockInContext(context, func, entry_name.as_ptr());
            let else_block = llvm::core::LLVMAppendBasicBlockInContext(context, func, entry_name.as_ptr());
            let merge_block = llvm::core::LLVMAppendBasicBlockInContext(context, func, entry_name.as_ptr());

            llvm::core::LLVMBuildCondBr(builder, is_nonzero, then_block, else_block);

            llvm::core::LLVMPositionBuilderAtEnd(builder, then_block);
            let mut then_return = zero;
            for expr in then_body {
                then_return = codegen_expr(context, builder, func, names, expr);
            }
            llvm::core::LLVMBuildBr(builder, merge_block);

            llvm::core::LLVMPositionBuilderAtEnd(builder, else_block);
            let mut else_return = zero;
            for expr in else_body {
                else_return = codegen_expr(context, builder, func, names, expr);
            }
            llvm::core::LLVMBuildBr(builder, merge_block);

            // Position the builder so that it's ready to work on the next
            // expression.
            llvm::core::LLVMPositionBuilderAtEnd(builder, merge_block);
            zero
        }
    }
}

Много кода, но он делает то, что мы ожидали. Теперь мы можем запустить такую программу:

a = 1
if a {
    a = 42
} else {
    a = 13
}
a

которая порождает такой IR:

; ModuleID = 'example_module'

define i64 @main() {
entry:
  %a = alloca i64
  store i64 1, i64* %a
  %a1 = load i64, i64* %a
  %is_nonzero = icmp ne i64 %a1, 0
  br i1 %is_nonzero, label %entry2, label %entry3

entry2:                                           ; preds = %entry
  store i64 42, i64* %a
  br label %entry4

entry3:                                           ; preds = %entry
  store i64 13, i64* %a
  br label %entry4

entry4:                                           ; preds = %entry3, %entry2
  %a5 = load i64, i64* %a
  ret i64 %a5
}

Однако мы пока не закончили. Сейчас наше «выражение» if всегда равно нулю. Вместо этого, if должно приравниваться к then_return если выполняется путь “then”, либо else_return в противном случае.

Как заставить LLVM отслеживать, какой путь был выполнен? С помощью узла “Phi”. Вы отдаёте инструкции phi список пар (блок, значение), и затем phi-узел будет возвращать значение, соответствующее блоку, который выполнялся перед ним.

На этом мы закончим с if. Замети, что мы должны обновить then_block и else_block. Это делается для того, чтобы получить последний блок в конструкции “then”/”else”, а ранее then_block был первым блоком в “then”/”else".

// ...
// This is mostly the same code as before, just note the new calls to
// `LLVMGetInsertBlock`.

llvm::core::LLVMPositionBuilderAtEnd(builder, then_block);
let mut then_return = zero;
for expr in then_body {
    then_return = codegen_expr(context, builder, func, names, expr);
}
llvm::core::LLVMBuildBr(builder, merge_block);
let then_block = llvm::core::LLVMGetInsertBlock(builder);

llvm::core::LLVMPositionBuilderAtEnd(builder, else_block);
let mut else_return = zero;
for expr in else_body {
    else_return = codegen_expr(context, builder, func, names, expr);
}
llvm::core::LLVMBuildBr(builder, merge_block);
let else_block = llvm::core::LLVMGetInsertBlock(builder);

// Insert the phi node
llvm::core::LLVMPositionBuilderAtEnd(builder, merge_block);
let phi_name = CString::new("iftmp").unwrap();
let phi = llvm::core::LLVMBuildPhi(builder, int_type, phi_name.as_ptr());

let mut values = vec![then_return, else_return];
let mut blocks = vec![then_block, else_block];

llvm::core::LLVMAddIncoming(phi, values.as_mut_ptr(), blocks.as_mut_ptr(), 2);
phi

И вот он, удивительный компилятор:

vagrant@vagrant:/vagrant$ cat in.ex
a = 1
b = 0
c = if a {
    if b {
        11
    } else {
        40
    }
} else {
    if b {
        10
    } else {
        20
    }
}
c + 2
vagrant@vagrant:/vagrant$ cargo run
     Running `target/debug/main`
vagrant@vagrant:/vagrant$ lli-3.8 out.ll ; echo $?
42

Круто! Вот код, который генерируется для данного примера входной программы:

; ModuleID = 'example_module'
define i64 @main() {
entry:
  %a = alloca i64
  %b = alloca i64
  %c = alloca i64
  store i64 1, i64* %a
  store i64 0, i64* %b
  %a1 = load i64, i64* %a
  %is_nonzero = icmp ne i64 %a1, 0
  br i1 %is_nonzero, label %entry2, label %entry3

entry2:                                           ; preds = %entry
  %b5 = load i64, i64* %b
  %is_nonzero6 = icmp ne i64 %b5, 0
  br i1 %is_nonzero6, label %entry7, label %entry8

entry3:                                           ; preds = %entry
  %b10 = load i64, i64* %b
  %is_nonzero11 = icmp ne i64 %b10, 0
  br i1 %is_nonzero11, label %entry12, label %entry13

entry4:                                           ; preds = %entry14, %entry9
  %iftmp16 = phi i64 [ %iftmp, %entry9 ], [ %iftmp15, %entry14 ]
  store i64 %iftmp16, i64* %c
  %c17 = load i64, i64* %c
  %addtmp = add i64 %c17, 2
  ret i64 %addtmp

entry7:                                           ; preds = %entry2
  br label %entry9

entry8:                                           ; preds = %entry2
  br label %entry9

entry9:                                           ; preds = %entry8, %entry7
  %iftmp = phi i64 [ 11, %entry7 ], [ 40, %entry8 ]
  br label %entry4

entry12:                                          ; preds = %entry3
  br label %entry14

entry13:                                          ; preds = %entry3
  br label %entry14

entry14:                                          ; preds = %entry13, %entry12
  %iftmp15 = phi i64 [ 10, %entry12 ], [ 20, %entry13 ]
  br label %entry4
}

Отметим паттерн, который образуют блоки: за исключением блока entry, они образуют группы по три, где первым идёт ветка «then», затем ветка «else», затем блок «merge» (который можно узнать по инструкции phi). Это следствие того факта, что каждый раз, когда мы обнаруживаем выражение «if», мы добавляем три новых блока в main. Тройки блоков расположены в том порядке, в котором происходит обход дерева AST.

Вот и всё! Надеюсь, что сейчас у вас есть достаточная основа, чтобы действовать самостоятельно.
Поделиться публикацией
AdBlock похитил этот баннер, но баннеры не зубы — отрастут

Подробнее
Реклама
Комментарии 20
  • +4

    Хорошее начало. Кстати, мне в свое время понравилось почитать исходники компилятора Crystal, там работа с LLVM сделана очень читаемо. https://github.com/crystal-lang/crystal/blob/master/src/compiler/crystal/codegen/codegen.cr

    • +1
      Спасибо за ссылку, будет полезно.
    • 0
      Не понимаю, как на расте вообще можно разрабатывать. Несколько раз пробовал — каждый раз бросал на год, думал «Ну тогда будет получше», проходил этот год, и ничего не происходила. Последняя третья попытка была на прошлых выходных. Может мозилла просто не любит винду и все идеально работает под никсами? Потому что под виндой я что только не делал, но работает исключительно в виде «блокнот с подсветкой». Автодополнение — в VS2017 работает, в VSCode уже нет, рейсер то работает, то отваливается, документация не прогружается… Все статьи на эту тему либо в стиле совы, либо просто не дают того эффекта, который обещают. В итоге похоже на веселое копание в стиле «html в блокноте, сохранить как 1.html и открыть в браузере», прикольно, детство вспоминается, но черт, Холмс, как?

      То есть тут человек берет и разрабатывает вполне себе серьезное приложение. Неужели он с такими кактусами это делает? А если нет, как он этого добился? Может действительно под линуксом всё чудесным образом нормально работает из коробки, и просто надо его поставить и разрабатывать под ним?..

      Очень хотелось поработать под растом… Но пока что не представляю, как это можно делать, не вырывая волосы. Пока что самым простым способом для меня остается play.rust-lang.org, и удивляют люди, которые такой сложный софт пишут в таких сложных условиях. Моё уважение.
      • +1
        А вы Eclipse не пробовали?
        • 0
          Нет, в принципе эклипс не очень люблю (еще когда джаву пробовал), и даже не знал, что под неё раст есть. Большинство примеров в интернете видел только на VSCode и аналогах.
      • +4

        Рейсер сам по себе уже давно не разрабатывается.
        Для VSCode — RLS и соответственно поставить RLS как компонент. Единственная проблема — пока что для этого надо ставить найтли тулчейн. Сижу и пишу частично под виндовс, проблем в целом не имею.

        • 0
          Спасибо, поставил, вроде получше, хотя в углу все равно висит раздражающая надпись rust toolchain is missing, при попытки установки которой все ломается. Вечером надо будет попробовать что-нибудь пописать.
          • 0
            в углу все равно висит раздражающая надпись rust toolchain is missing, при попытки установки которой все ломается

            а rust через rustup установлен?

            • 0
              Да
              • 0
                Поставил на другой машине — более успешно, RLS работает и даже подсказывает что-то правильно. Но вот с запуском дебаггера беда. Как это настраивается?

                (спросил в гиттере тоже, но имхо как коммент будет полезно другим начинающим)
                • +2
                  Отвечу сам себе — есть неплохая инструкция по настройке дебаггера ( www.brycevandyk.com/debug-rust-on-windows-with-visual-studio-code-and-the-msvc-debugger ), но проблема в том, что для билда все равно нужно руками вызывать cargo build, плагину требуется указывать путь до уже скомпилированного файла…
                  • +2
                    С этой проблемой тоже справился, делаем такой launch.json
                    {
                            "version": "0.2.0",
                            "configurations": [
                    
                                {
                                    "name": "(Windows) Launch",
                                    "type": "cppvsdbg",
                                    "request": "launch",
                                    "program": "${workspaceRoot}/target/debug/hello_world.exe",
                                    "preLaunchTask": "cargo build",
                                    "args": [],
                                    "stopAtEntry": false,
                                    "cwd": "${workspaceRoot}",
                                    "environment": [],
                                    "externalConsole": true
                                }
                            ]
                        }

                    Рядом кладем tasks.json
                    {
                        // See https://go.microsoft.com/fwlink/?LinkId=733558
                        // for the documentation about the tasks.json format
                        "version": "2.0.0",
                        "tasks": [
                            {
                                "type": "shell",
                                "taskName": "cargo build",
                                "command": "cargo",
                                "args": [
                                    "build"
                                ],
                                "problemMatcher": [
                                    "$rustc"
                                ]
                            }
                        ]
                    }


                    Теперь все должно нормально собираться перед дебагом.
          • +3

            Пишу в linux/osx используя vscode+rls-vscode — вроде основные вещи с RLS уже вполне сносно работают.


            Много кто еще хвалит https://intellij-rust.github.io .

            • 0
              О, а вот это хорошая штука. На студии плагин не завелся, может на идее лучше заработает. Спасибо, попробую.
              • 0

                Я им пользуюсь около года, там стабильный хороший прогресс. Не всё гладко пока, но, в целом, неплохо.

              • 0
                Посмотрите в сторону Sublime подсветка и build system есть из коробки. Racer прикручивается с помощью плагина Rust​Auto​Complete за пару секунд (сам пробовал под Win и Mac).
              • 0
                А как Вы потом «дебагаете» полученный код, к Эллипсу что-то прикручивается?
                Насколько всё гладко с дебагингом получается?
                • 0
                  Это перевод, вообще-то.
                  • 0
                    Может кто из читающих что отпишет, был ли опыт дебага.

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