Пользователь
0,3
рейтинг
6 июня 2014 в 07:16

Разработка → Сравнение Rust и С++ на примерах

Rust*, C++*

Предисловие


Вот и обещанное сравнение языков. Примеры, конечно, искуственные, так что используйте своё воображение, чтобы оценить масштабы угрозы в реальном мире.

Все C++ программы были собраны при помощи gcc-4.7.2 в режиме c++11, используя online compiler. Программы на Rust были собраны последней версией Rust (nightly, 0.11-pre), используя rust playpen.

Я знаю, что C++14 (и далее) будет залатывать слабые места языка, а также добавлять новые возможности. Размышления на тему того, как обратная совместимость мешает C++ достичь звёзд (и мешает ли), выходят за рамки данной статьи, однако мне будет интересно почитать Ваше экспертное мнение в комментариях. Также приветствуется любая информация о D.


Проверка типов шаблона


Автор С++ уже давно недоволен тем, как шаблоны реализованы в языке, назвав их "compile-time duck typing" в недавнем выступлении на Lang-NEXT. Проблема заключается в том, что не всегда понятно, чем инстанцировать шаблон, глядя на его объявление. Ситуация ухудшается монстрообразными сообщениями об ошибках. Попробуйте собрать, к примеру, вот такую программу:
#include <vector>
#include <algorithm>
int main()
{
    int a;
    std::vector< std::vector <int> > v;
    std::vector< std::vector <int> >::const_iterator it = std::find( v.begin(), v.end(), a );
}

Представьте себе радость человека, читающего многостраничное сообщение об ошибке, если он создал такую ситуацию случайно.

Шаблоны в Rust проверяются на корректность до их инстанцирования, поэтому есть чёткое разделение между ошибками в самом шаблоне (которых быть не должно, если Вы используете чужой/библиотечный шаблон) и в месте инстанцирования, где всё, что от Вас требуется — это удовлетворить требования к типу, описанные в шаблоне:
trait Sortable {}
fn sort<T: Sortable>(array: &mut [T]) {}
fn main() {
    sort(&mut [1,2,3]);
}

Этот код не собирается по очевидной причине:
demo:5:5: 5:9 error: failed to find an implementation of trait Sortable for int
demo:5 sort(&mut [1,2,3]);


Обращение к удалённой памяти


Существует целый класс проблем с С++, выражающихся в неопределённом поведении и падениях, которые возникают из-за попытки использовать уже удалённую память.
Пример:
int main() {
    int *x = new int(1);
    delete x;
    *x = 0;
}

В Rust такого рода проблемы невозможны, так как не существует команд удаления памяти. Память на стеке живёт, пока она в области видимости, и Rust не допускает, чтобы ссылки на неё пережили эту область (смотрите пример про потерявшийся указатель). Если же память выделена в куче — то указатель на неё (
Box) ведёт себя точно так же, как и обычная переменная на стеке (удаляется при выходе из зоны видимости). Для совместного использования данных есть подсчёт ссылок (std::rc::Rc) и сборщик мусора (std::gc::Gc), оба реализованы как сторонние классы (Вы можете написать свои).

Потерявшийся указатель на локальную переменную


Версия С++:
#include <stdio.h> int *bar(int *p) { return p; } int* foo(int n) { return bar(&n); } int main() { int *p1 = foo(1); int *p2 = foo(2); printf("%d, %d\n", *p1, *p2); }

На выходе:
2, 2

Версия Rust:
fn bar<'a>(p: &'a int) -> &'a int {
    return p;
}
fn foo(n: int) -> &int {
    bar(&n)
}
fn main() {
    let p1 = foo(1);
    let p2 = foo(2);
    println!("{}, {}", *p1, *p2);
}

Ругательства компилятора:
demo:5:10: 5:11 error: `n` does not live long enough
demo:5 bar(&n)
^
demo:4:24: 6:2 note: reference must be valid for the anonymous lifetime #1 defined on the block at 4:23...
demo:4 fn foo(n: int) -> &int {
demo:5 bar(&n)
demo:6 }
demo:4:24: 6:2 note: ...but borrowed value is only valid for the block at 4:23
demo:4 fn foo(n: int) -> &int {
demo:5 bar(&n)
demo:6 }

Неинициированные переменные


#include <stdio.h>
int minval(int *A, int n) {
  int currmin;
  for (int i=0; i<n; i++)
    if (A[i] < currmin)
      currmin = A[i];
  return currmin;
}
int main() {
    int A[] = {1,2,3};
    int min = minval(A,3);
    printf("%d\n", min);
}

Выдаёт мне 0 на выходе, хотя на самом деле здесь, конечно, неопределённый результат. А вот то же самое на Rust (прямой не-идиоматичный перевод):
fn minval(A: &[int]) -> int {
  let mut currmin;
  for a in A.iter() {
    if *a < currmin {
      currmin = *a;
    }
  }
  currmin
}
fn main() {
    let A = [1i,2i,3i];
    let min = minval(A.as_slice());
    println!("{}", min);
}

Не собирается, ошибка:
use of possibly uninitialized variable: `currmin`

Более идиоматичный (и работающий) вариант этой функции выглядел бы так:
fn minval(A: &[int]) -> int {
  A.iter().fold(A[0], |u,&a| {
    if a<u {a} else {u}
  })
}


Неявный конструктор копирования


struct A{
    int *x;
    A(int v): x(new int(v)) {}
    ~A() {delete x;}
};

int main() {
    A a(1), b=a;
}

Собирается, однако падает при выполнении:
*** glibc detected *** demo: double free or corruption (fasttop): 0x0000000000601010 ***

То же самое на Rust:
struct A{
    x: Box<int>
}
impl A {
    pub fn new(v: int) -> A {
        A{ x: box v }
    }
}
impl Drop for A {
    fn drop(&mut self) {} //нет необходимости, приведено для точной копии С++
}
fn main() {
    let a = A::new(1);
    let _b = a;
}

Собирается и выполняется без ошибки. Копирования не происходит, ибо объект не реализует trait Copy.
Rust ничего за Вашей спиной делать не будет. Хотите автоматическую реализацию Eq или Clone? Просто добавьте свойство deriving к Вашей структуре:
#[deriving(Clone, Eq, Hash, PartialEq, PartialOrd, Ord, Show)]
struct A{
    x: Box<int>
}


Перекрытие области памяти


#include <stdio.h>
struct X {  int a, b; };

void swap_from(X& x, const X& y) {
    x.a = y.b; x.b = y.a;
}
int main() {
    X x = {1,2};
    swap_from(x,x);
    printf("%d,%d\n", x.a, x.b);
}

Выдаёт нам:
2,2

Функция явно не ожидает, что ей передадут ccылки на один и тот же объект. Чтобы убедить компилятор, что ссылки уникальные, в С99 придумали restrict, однако он служит лишь подсказкой оптимизатору и не гарантирует Вам отсутствия перекрытий: программа будет собираться и исполняться как и раньше.

Попробуем сделать то же самое на Rust:
struct X { pub a: int, pub b: int }
fn swap_from(x: &mut X, y: &X) {
    x.a = y.b; x.b = y.a;
}
fn main() {
    let mut x = X{a:1, b:2};
    swap_from(&mut x, &x);
}

Выдаёт нам следующее ругательство:
demo:7:24: 7:25 error: cannot borrow `x` as immutable because it is also borrowed as mutable
demo:7 swap_from(&mut x, &x);
^
demo:7:20: 7:21 note: previous borrow of `x` occurs here; the mutable borrow prevents subsequent moves, borrows, or modification of `x` until the borrow ends
demo:7 swap_from(&mut x, &x);
^
demo:7:26: 7:26 note: previous borrow ends here
demo:7 swap_from(&mut x, &x);

Как видим, компилятор не позволяет нам ссылаться на одну и ту же переменную через "&mut" и "&" одновременно, тем самым гарантируя, что изменяемую переменную никто другой не сможет прочитать или изменить, пока действительна &mut ссылка. Эти гарантии обсчитываются в процессе сборки и не замедляют выполнение самой программы. Более того, этот код собирается так, как если бы мы на C99 использовали restrict указатели (Rust предоставляет LLVM информацию об уникальности ссылок), что развязывает руки оптимизатору.

Испорченный итератор


#include <vector>
int main() {
    std::vector<int> v;
    v.push_back(1);
    v.push_back(2);
    for(std::vector<int>::const_iterator it=v.begin(); it!=v.end(); ++it) {
        if (*it < 5)
            v.push_back(5-*it);
    }
}

Код собирается без ошибок, однако при запуске падает:
Segmentation fault (core dumped)

Попробуем перевести на Rust:
fn main() {
    let mut v: Vec<int> = Vec::new();
    v.push(1);
    v.push(2);
    for x in v.iter() {
        if *x < 5 {
            v.push(5-*x);
        }
    }
}

Компилятор не позволяет нам это запустить, вежливо указав, что изменять вектор в процессе его обхода нельзя:
demo:7:13: 7:14 error: cannot borrow `v` as mutable because it is also borrowed as immutable
demo:7 v.push(5-*x);
^
demo:5:14: 5:15 note: previous borrow of `v` occurs here; the immutable borrow prevents subsequent moves or mutable borrows of `v` until the borrow ends
demo:5 for x in v.iter() {
^
demo:10:2: 10:2 note: previous borrow ends here
demo:5 for x in v.iter() {
demo:6 if *x < 5 {
demo:7 v.push(5-*x);
demo:8 }
demo:9 }
demo:10 }


Опасный Switch


#include <stdio.h>
enum {RED, BLUE, GRAY, UNKNOWN} color = GRAY;
int main() {
  int x;
  switch(color) {
    case GRAY: x=1;
    case RED:
    case BLUE: x=2;
  }
  printf("%d", x);
}

Выдаёт нам "2". В Rust жы Вы обязаны перечислить все варианты при сопоставлении с образцом. Кроме того, код автоматически не прыгает на следующий вариант, если не встретит break. Правильная реализация на Rust будет выглядеть так:
enum Color {RED, BLUE, GRAY, UNKNOWN}
fn main() {
  let color = GRAY;
  let x = match color {
      GRAY => 1,
      RED | BLUE => 2,
      _ => 3,
  };
  println!("{}", x);
}


Случайная точка с запятой

int main() {
  int pixels = 1;
  for (int j=0; j<5; j++);
    pixels++;
}

В Rust Вы обязаны заключать тела циклов и сравнений в фигурные скобки. Мелочь, конечно, но одим классом ошибок меньше.

Многопоточность


#include <stdio.h>
#include <pthread.h>
#include <unistd.h>

class Resource {
    int *value;
public:
    Resource(): value(NULL) {}
    ~Resource() {delete value;}
    int *acquire() {
        if (!value) {
            value = new int(0);
        }
        return value;
    }
};

void* function(void *param) {
    int *value = ((Resource*)param)->acquire();
    printf("resource: %p\n", (void*)value);
    return value;
}

int main() {
    Resource res;
    for (int i=0; i<5; ++i) {
        pthread_t pt;
        pthread_create(&pt, NULL, function, &res);
    }
    //sleep(10);
    printf("done\n");
}

Порождает несколько ресурсов вместо одного:
done
resource: 0x7f229c0008c0
resource: 0x7f22840008c0
resource: 0x7f228c0008c0
resource: 0x7f22940008c0
resource: 0x7f227c0008c0

Это типичная проблема синхронизации потоков, которая возникает при одновременном изменении объекта несколькими потоками. Попробуем написать то же на Rust:
struct Resource {
    value: Option<int>,
}
impl Resource {
    pub fn new() -> Resource {
        Resource{ value: None }
    }
    pub fn acquire<'a>(&'a mut self) -> &'a int {
        if self.value.is_none() {
            self.value = Some(1);
        }
        self.value.get_ref()
    }
}

fn main() {
    let mut res = Resource::new();
    for _ in range(0,5) {
        spawn(proc() {
            let ptr = res.acquire();
            println!("resource {}", ptr)
        })
    }
}

Получаем ругательство, ибо нельзя вот так просто взять и мутировать общий для потоков объект.
demo:20:23: 20:26 error: cannot borrow immutable captured outer variable in a proc `res` as mutable
demo:20 let ptr = res.acquire();

Вот так может выглядеть причёсанный код, который удовлетворяет компилятор:
extern crate sync;
use sync::{Arc, RWLock};

struct Resource {
    value: Option<Box<int>>,
}
impl Resource {
    pub fn new() -> Resource {
        Resource{ value: None }
    }
    pub fn acquire(&mut self) -> *int {
        if self.value.is_none() {
            self.value = Some(box 1)
        }
        &**self.value.get_ref() as *int
    }
}

fn main() {
    let arc_res = Arc::new(RWLock::new(Resource::new()));
    for _ in range(0,5) {
        let child_res = arc_res.clone();
        spawn(proc() {
            let ptr = child_res.write().acquire();
            println!("resource: {}", ptr)
        })
    }
}

Он использует примитивы синхронизации Arc (Atomically Reference Counted - для доступа к тому же объекту разными потоками) и RWLock (для блокировки совместного изменения). На выходе получаем:
resource: 0x7ff4b0010378
resource: 0x7ff4b0010378
resource: 0x7ff4b0010378
resource: 0x7ff4b0010378
resource: 0x7ff4b0010378

Понятное дело, что на С++ тоже можно написать правильно. И на ассемблере можно. Rust просто не даёт Вам выстрелить себе в ногу, оберегая от собственных ошибок. Как правило, если программа собирается, значит она работает. Лучше потерять полчаса на приведение кода в приемлимый для компилятора вид, чем потом месяцами отлаживать ошибки синхронизации (стоимость исправления дефекта).

Немного про небезопасный код


Rust позволяет Вам играть с голыми указателями сколько угодно, но только внутри блока unsafe{}. Это тот случай, когда Вы говорите компилятору "Не мешай! Я знаю, что делаю.". К примеру, все "чужие" функции (из написанной на С библиотеки, с которой вы сливаетесь) автоматически маркируются как опасные. Философия языка в том, чтобы маленькие куски небезопасного кода были изолированы от основной части (нормального кода) безопасными интерфейсами. Так, например, небезопасные участки можно обнаружить в реализациях классов Cell и Mutex. Изоляция опасного кода позволяет не только значительно сузить область поиска неожиданно возникшей проблемы, но и хорошенько покрыть его тестами (мы дружим с TDD!).

Источники


Guaranteeing Memory Safety in Rust (by Niko Matsakis)
Rust: Safe Systems Programming with the Fun of FP (by Felix Klock II)
Lang-NEXT: What – if anything – have we learned from C++? (by Bjarne Stroustrup)
Lang-NEXT Panel: Systems Programming in 2014 and Beyond
Dzmitry Malyshau @kvark
карма
69,7
рейтинг 0,3
Реклама помогает поддерживать и развивать наши сервисы

Подробнее
Реклама

Самое читаемое Разработка

Комментарии (96)

  • –35
    Вся память на стеке? Язык для программ уровня Hello World? Даже без кучи? Без сборщика мусора? Ну и зачем такое счастье? Дальше лучше, говорим про С++, юзаем корявую функцию из C, при этом аналог из C++ куда удобнее, но нет же, нельзя ведь его привести, не выгодно для статьи (если что я про printf, даже и не помню когда юзал).

    Ну и за корявый код вините не язык, а руки разработчика, тонкости он должен знать.
    • +7
      Из текста:
      Если же память выделена в куче — то указатель на неё (Box) ведёт себя точно так же, как и обычная переменная на стеке (удаляется при выходе из зоны видимости). Для совместного использования данных есть подсчёт ссылок (std::rc::Rc) и сборщик мусора (std::gc::Gc), оба реализованы как сторонние классы (Вы можете написать свои).
    • +5
      Я так понимаю, в примерах printf никакого отношения к обсуждаемой проблеме не имеет и используется только вывода результата. Использование стримов — это конечно хорошо, но неужели это тут так принципиально?
    • +5
      Вы читали статью вообще? В Rust есть и куча, и сборщики мусора (ну сейчас там в стандартной библиотеке не полноценный GC, а подсчёт ссылок, но это дело желания и умения — написать нормальный GC). При этом все эти механизмы безопасны в принципе. А в C++ ничего не мешает вам, например, взять указатель во внутренности unique_ptr. Я уже не говорю про более сложные случаи, связанные с многопоточностью.
  • +3
    Я бы порекомендовал D. В нём тёплый ламповый сишный синтаксис (а не CoffeScript`овый как в Rust), да и вкусных плюшей поболее.

    dlang.org/spec.html
    • +4
      я правильно понимаю, что GC в нём по-умолчанию включён?

      P.S. Никто кстати не желает забабахать подобное сравнение про D?
      • 0
        Да, по-умолчанию включён. Но его можно и отключить или написать свой.
        • +6
          Если в D отключить сборщик мусора, то стандартная библиотека перестанет нормально функционировать. Кроме того, если вам стандартная библиотека и не нужна, то, отключая GC, вы возвращаетесь в мир проблем C++, так хорошо описанный автором статьи.

          В Rust изначально заложена возможность писать безопасные программы без сборщиков мусора и других автоматических систем управления памятью.
          • 0
            Если так, то слабо верится, что разработчики языка смогут его оперативно поддерживать.
      • +6
        Никто кстати не желает забабахать подобное сравнение про D?


        Сохранил первичную структуру поста. habrahabr.ru/post/225507/
        Сравнение получается не сильно честным, примеры подобраны под сильный статический анализатор, встроенный в Rust, а D в этой области слабее.
        • +2
          Спасибо, это было великолепно!
          Пора объявлять неделю языков программирования на Хабре. Небойсь, и Swift объявится ;)
      • 0
        После того, как Мейерс выступил на ДиКонф 2014, появилась вот такая интерпретация его выступления:
        he-the-great.livejournal.com/52333.html
    • +7
      Очень странно, что вы увидели в Rust CoffeeScript'овый синтаксис. В Rust используются стандартные для C блоки для выделения кода, например. Да он вообще не похож на CoffeeScript!

      А насчёт плюшек — тут можно спорить. Например, лично мне не нравятся строковые миксины, пусть они и офигенно мощные. А в Rust есть нормальные синтаксические гигиеничные макросы (и возможность создания полноценных процедурных макросов, как в Scala, например).
      • +1
        cube = (x) -> square(x) * x

        fn foo(n: int) -> &int {
        bar(&n)
        }

        Небольшая схожесть всё же есть. Странное объявление функций + отсутствие скобок в некоторых местах.

        Мне тоже не нравятся строковые миксины. Но только потому, что подозреваю, что они будут работать медленнее. Есть множество других плюшек, ссылку я уже оставил. Если говорить про безопасность, в D есть модификатор safe, который не позволит сделать многие неприличные вещи.

        И Rust, и D, и GO мощные и современные языки, которые лишены многих недостатков C++. Поэтому, думаю, что можно спокойно выбирать язык с тем синтаксисом, который больше нравится.
        • +2
          Ну схожесть разве что в стрелках в функциях и в statement as expression. Но последним обладают многие языки, та же Scala например.
          И Rust, и D, и GO мощные и современные языки, которые лишены многих недостатков C++. Поэтому, думаю, что можно спокойно выбирать язык с тем синтаксисом, который больше нравится.

          Полностью согласен.
    • +1
      Регионов в D нет. В системном программировании они будут очень полезны.
  • +4
    > Размышления на тему того, как обратная совместимость мешает C++ достичь звёзд…

    А ведь автор в чём-то прав. Вот например такие абсолютно неочевидные хрени буквально рвут мозг (особенно когда первый раз с этим сталкиваешься):

    habrahabr.ru/post/68796/

    А для чего это? Правильно, для обратной совместимости со старыми C программами (привет из 80-х).
  • +2
    Спасибо за статью!

    Я бы ещё добавил, наверное, сравнение с С++ными примитивами для управления памятью — unique_ptr, shared_ptr и прочее, и указанием, почему всё-таки они не панацея. Потому что это довольно справедливая претензия — в современном С++ код через сырые указатели стараются не писать.
  • 0
    Вот здесь один из авторов языка (фактически, современную систему с &/&mut указателями и borrow checker'ом заложил именно он) рассказывает, как именно обеспечивается безопасность работы с памятью в Rust, и какого класса ошибки исключаются. Очень интересный доклад, интересующимся советую посмотреть.

    Это первая из ссылок-источников в посте, просто хочу обратить на неё внимание.
  • +7
    Дошел до места, где автор сравнивает память, выделенную через new, с памятью на стеке. Дальше не читал.
    Прежде чем писать о языке программирования, разберитесь в нем. Не надо заниматься распространением безграмотности, пожалуйста :)
    • +2
      А можно конкретнее? Я сравнил память из кучи и на стеке с точки зрения её управления в Rust. Может, если я плохо выразился, попробуйте Вы объяснить лучше.
  • +6
    1) Монстрообразные сообщения об ошибках это не проблема языка это проблема gcc. В clang-сообщение уже более понятно. Итого как плюс язык пункт номер 1 выбывает
    2)Битые указатели на стековые переменные — это то, от чего программист на С++ избавляется очень быстро (Может ошибиться раз или два, но третей подобной ошибки он не допустит). Нужен подсчет ссылок — shaerd_ptr
    3) Обращение к удаленной памяти отлавливается Valgrind и статическими анализаторами на раз-два.
    4) Не полное перечисление в свитче — gcc и clang это отслеживают.
    5) Многопоточность. Решается введением shared_ptr
    • +1
      По поводу (1) еще раз дам ссылку, которую уже постил ранее: blog.llvm.org/2010/04/amazing-feats-of-clang-error-recovery.html

      P.S.: Касательно самого утверждения можно поспорить. Автор отмечает особенности языка, который вылавливает подобные ошибки на лексическом уровне. Это все таки более круто, поскольку не накладывает на программиста дополнительных неявных или слабо формализованных ограничений.
      • 0
        Посмотрел на синтаксис Rust, вот так сразу из записи я не могу определить ошибку => все упирается в сообщение компилятора.

        И да, я не понял к чему ссылка: в целях ознакомления или аргумент в споре? Если последнее, то тогда в каком?
        • 0
          к чему ссылка
          Целях ознакомления; в подтверждение вашего пункта 1.

          из записи я не могу определить ошибку => все упирается в сообщение компилятора.
          Это то же самое, что утверждать что в С++ const не нужен, мол, из записи я не могу определить ошибку, приходится читать сообщения компилятора. Под лексическим уровнем я понимаю то, что компилятор обладает достаточной информацией, чтобы проводить анализ того, что в других языках остается «за кадром», в сознании программиста.

          Например, в C++ мы вынуждены сознательно контролировать вопросы синхронизации ресурсов в многопоточном приложении. Тут компилятор ничего не подскажет. Говорить что программист сам дурак и программа должна быть написана так чтобы все было очевидно тоже нельзя, по скольку ошибки обычно возникают не при проектировании а при рефакторинге и изменении чужого кода.

          Даже зная тонкости, я был бы не против, если компилятор сможет отлавливать противоречия и в подобных местах.
          • 0
            А кто говорит что что-то не нужно, просто это не киллер-фича. Вот ошибки с многопоточностью — это да.
    • +2
      5) Многопоточность. Решается введением shared_ptr

      Как?
      • +1
        Ну что-то типа того:

        class Resource {
        shared_ptr value;
        public:
        Resource(): {}
        ~Resource() }
        int *acquire() {
        return atomic_load(&value);
        }
        };

        Но в Qt это сделано более удобно
        • +1
          Спасибо, направление понятно:
          Инициализировать через atomic_compare_exchange_strong_explicit, а читать через atomic_load

          Правда надо признать, shared_ptr тут не нужен. Можно обойтись простым указателем :)
  • 0
    Для совместного использования данных есть подсчёт ссылок (std::rc::Rc) и сборщик мусора (std::gc::Gc), оба реализованы как сторонние классы (Вы можете написать свои).Как написать свои классы без операции «удаления памяти»? Эта операция в языке есть, просто вы не нашли ее.
    • 0
      Извиняюсь, закрывающий тег уехал, поздно заметил.
      Для совместного использования данных есть подсчёт ссылок (std::rc::Rc) и сборщик мусора (std::gc::Gc), оба реализованы как сторонние классы (Вы можете написать свои).
      Как написать свои классы без операции «удаления памяти»? Эта операция в языке есть, просто вы не нашли ее.
    • 0
      Реализация Rc и Gc содержит небезопасный код для управления памятью. В области безопасного кода операции «удаления» нет, именно это имелось ввиду.
  • +2
    Какой-то чувак сейчас пишет серию статей на схожую тему, Rust для программистов на C++. featherweightmusings.blogspot.com/
  • –13
    По-моему, гораздо проще изучить грабли С++, чем все эти новомодные языки: Julia, D, Rust, Swift (потом наверняка ещё что-нибудь придумают).
    • +4
      Когда-то так думали и труъ программисты, не признававшие ничего выше ассемблера. Особенно во время, когда компиляторы действительно генерировали код хуже человека.
  • +15
    Если бы в Rust'е еще синтаксис был более человеческим, а не эти кучи закорючек и сокращений в стиле Perl.
    • +4
      Во-во. Долго вникал в смысл 'a' в этой строке:
      fn bar<'a>(p: &'a int) -> &'a int {
      

      А еще на лямбда-синтаксис [](){} ругаются.
      • 0
        Можно просто писать: []{}
    • +3
      Как ни странно, в этом желании разработчики вас поддерживают. Поэтому в языке закорючек стало гораздо меньше по сравнению с первой версией языка, как и сокращений. Например, ещё в начале года owned pointer записывался как ~T, например:
      struct A {
          x: ~int
      }
      

      теперь вместо тильды используется Box<T>, который, собственно, в статье используется. А в прошлом году из языка удалили @, который обозначал как раз указатель за сборщиком мусора.

      А больше в языке ничего сверхкраткого с закорючками нет.
    • 0
      Вас смутили области видимости. Если есть едеи, как описать их более человечно — делитесь ;)
  • +2
    Всеми силами надеюсь что Rust будет годнотой и избавит мир от цпп кода.
    • –4
      Что мешает использовать Go?
      • +7
        Не хочу возобновлять священные войны, но Go не подходит для системного программирования, так что это не конкурент C++ и Rust. Этой темы мы уже касались в комментариях моей прошлой статьи.
      • +6
        Вы не сможете написать на Go, например, прошивку для микроконтроллера — рантайм слишком большой и ресурсоёмкий, а на Rust — вполне, если отключить его рантайм. В Go рантайм отключить хоть и можно, но бесполезно — вся безопасность работы с памятью, обеспеченная сборщиком мусора, потеряется. В Go также нет сырых указателей, которые очень важны для работы с железом. Да, там есть unsafe.Pointer, но работа с ними вызывает адскую боль. Это одно из объяснений.
      • +1
        Система типов и управление памятью.
  • 0
    Пример про потоки совсем за уши притянут, Вы намеренно написали не thread-safe код, конечно он может проинициализировпть память несколько раз, это обычный race condition.
    • +4
      Вот от такого «обычного» race condition и спасает нас компилятор Rust. Повторюсь, смысл не в том, что на С++/asm нельзя написать хорошо, а в том, что на Rust вы обязаны написать хорошо, а иначе оно не соберётся. Многопоточность — лишь частный случай, но без неё картина была бы неполной.
  • +4
    Я честно говоря ожидал другую статью, например о возможностях Rust'a которые сильно бы облегчали разработку по сравнению с С++
    А то что в этой статье, так это примеры того как Rust защищает программиста от ошибок которые он и так не должен допускать (если это конечно нормальный программист).
    Т.к. все примеры ошибок имхо высосаны из пальца, их допускают либо новички которые только только перешли на С++, либо криворукие которым не дано.

    Напишите пожалуйста нормальную статью, потому что как мне кажется Rust достоин большего внимания, чем просто подгузник для программиста.
    • +4
      Я честно говоря ожидал другую статью, например о возможностях Rust'a которые сильно бы облегчали разработку по сравнению с С++

      Главная возможность — защита от самопрострела ноги, и о ней я более-менее рассказал тут на примерах. Поверьте, оно сильно облегчает разработку ;)
      их допускают либо новички которые только только перешли на С++, либо криворукие которым не дано.

      Меня всегда заявляют такие высказывания. Как будто бы эти люди, которые криворукие, они где-то там, в другом мире, а мы работаем только с высшим сословием. Константин, приходилось ли Вам работать с большим С++ проектом? Мне — да, и я с горечью спешу Вас огорчить, что ошибки такого рода (простые, аналогично примерам из статьи) повсеместны. Они приносят уйму головной боли, проедают наше время и деньги на исправление.
      • 0
        Простите, заявляют забавляют
        • +3
          > Главная возможность — защита от самопрострела ноги, и о ней я более-менее рассказал тут на примерах. Поверьте, оно сильно облегчает разработку ;)
          То что я увидел я бы не назвал защитой от самострела, т.к. примеры, реально очень сильно надуманны.

          > Константин, приходилось ли Вам работать с большим С++ проектом?
          О да, не просто с большим, с гигантским.

          > Мне — да, и я с горечью спешу Вас огорчить, что ошибки такого рода (простые, аналогично примерам из статьи) повсеместны. Они приносят уйму головной боли, проедают наше время и деньги на исправление.

          Тогда давайте и я вас огорчу, подобные ошибки в 90% случаев отсеиваются варнингами, тестами, элементарным запуском на проверку, и ревью кода перед коммитом, не говоря уже об анализаторах, а если у вас в проекте допускаются подобные ошибки (; после for или возврат указателя на локальную переменную), и они каким-то образом проходят в репозиторий, то это означает что у вас проблемы как с программистами так и с менеджментом, сочувствую.

          Плохому программисту не язык мешает.
          • 0
            Чем раньше обнаруживается ошибка, тем меньше на нее тратится времени и денег. Ваш К. О.
            • +1
              Так никто об этом и не спорит, я лишь говорю что примеры в статье надуманны, автор также это признаёт ниже. Мне очень импонирует Rust и хотелось бы прочитать статью про реально полезные фишки этого языка которые делают его привлекательнее С++, а не то что он способен предотвращать банальные ошибки.
              • +3
                Способность предотвращать банальные ошибки — это и есть реально полезная фишка. Не вызвать деструкторы вручную. Еще есть сопоставление с образцом, но об этом уже моветон упоминать. По большому счету, это все, по сравнению с C++11/14. Я не понимаю, чего вы ждете, кнопку «сделать все как я хочу и без багов»?
          • +1
            Правда в том, что «проблемы как с программистами так и с менеджментом» есть везде. Проекты на С++, над которыми я работал (и работаю) профессионально, просто кишат велосипедами(свои умные указатели и контейнеры), голыми указателями, они не покрыты тестами и не проверяются статическими анализаторами. STL стараются избегать за милю, хотя это уже отдельная история… Эти проекты огромны (~35Mb собранный в release исполняемый файл), над ними работает туча людей, и ошибки появляются и исправляются постоянно.

            Я знаю, что можно писать грамотно, использовать стандартные умные указатели и контейнеры (и в том же духе), но реальность такова, что свобода выбора этих инструментов и лёгкий доступ к самострелам играют злую шутку над разработчиками. Даже в Вашей неподтверждённой статистике 10% оставшихся ошибок могут занять больше времени и денег на исправление, чем написание и первичная отладка самого кода, содержащего их.

            >Плохому программисту не язык мешает.
            При правильном выборе инструмента (языка), плохих программистов становится заметно меньше.
            • 0
              Часто отказ от c++ standard library обоснован, например в геймдеве, посмотрите, например, на ea stl. А другие команды пишут свои велосипеды.
              Вот например нужен мне lock-free hashmap для игры, где мне его взять? Вероятнее всего написать самому.
      • 0
        --защита от самопрострела ноги

        для этого уже есть C++/CLI

        когда новичек вырастет до профи он будет использовать STL + юнит тесты и самопристрелы будут теоритически невозможны
        • +1
          Однако C++/CLI — это уже совсем другая история. На нём каши не сваришь ОС не напишешь…

          А по поводу STL + unit tests, как уже говорилось, некоторые серьёзные проекты не используют их (игры!), так что дело не в квалификации. Да и самострелы не уходят «теоретически» — смотрите пример с итератором.
    • +3
      >которые он и так не должен допускать (если это конечно нормальный программист)
      Нормальный программист как нормальный человек просто обязан допускать ошибки. Без ошибок человек не человек.
  • +2
    Все-таки самая большая проблема в cpp и c это отсутствие модулей, остальное вполне лечится новыми стандартами.
    • 0
      Если я не ошибаюсь, модули Бъярн тоже хочет вылечить новым стандартом. Он об этом говорил на Lang-NEXT.
  • –2
    используйте своё воображение, чтобы оценить масштабы угрозы в реальном мире

    Почему-то меня не покидает ощущение, что в реальном мире у Rust найдутся свои грабли, для обхода которых будут делать еще один новый язык.

    Может настало время принять очевидное: разработчик програмного обеспечения должен обладать высокой квалификацией и опытом хождения по граблям, в противном случае никакие языки программирования не спасут от кривого падучего кода?
    • +3
      Поскольку Rust — системный язык, там есть «задний ход» в небезопасный низкоуровневый мир — unsafe, где возможны небезопасные операции, вроде разыменования сырых указателей. Если программист будет пользоваться этими блоками направо и налево, он получит фактически C, где возможно всё, в том числе расстрелять себе ноги урановыми медведями.

      Но сам язык сделан так (и на это сильный упор в его документации), что эти блоки нужно использовать только тогда, когда это реально необходимо (взаимодействие с C, например, или реализация высокоуровневых абстракций вроде Rc). Вне этих блоков ошибки, связанные с неправильной работой с памятью, исключены (с поправкой на возможные ошибки компилятора и баги в библиотеках). Поэтому, хоть языки и не могут в принципе спасти от кривого кода, они очень сильно могут облегчить когнитивную нагрузку и исключить наиболее распространённые ошибка. Если бы это было не так — мы бы до сих пор писали бы на ассемблере.
    • +2
      This is your last chance. After this, there is no turning back. You take the blue pill – the story ends, you wake up in your bed and believe whatever you want to believe. You take the red pill – you stay in Wonderland, and I show you how deep the rabbit hole goes. Remember, all I'm offering is the truth – nothing more.

      Реальный мир для Rust наступает с каждым днём. Пока не видно и намёка на то, что реализованная модель безопасности работает плохо. Выясняются детали, полируются интерфесы, добавляются возможности, а в целом — по Ленину:
      Верной дорогой идете, товарищи!
      • 0
        Тоже самое говорили про D, вот только порт Qt не обновляется с декабря… Меня это как-то резко напрягло
        • +1
          Я бы не отказался почитать на Хабре полноценный обзор D. Где язык находится сейчас, есть ли список живых проектов, как на D сказывается влияние Андрея Александреску, и насколько эффективно его применяют в Facebook (и зачем)?
    • +1
      Идеал недостижим, это не означает, что не надо к нему двигаться. Заметили часто встречающиеся грабли — исправили, обратили внимание на следующие (которые могут появиться и из-за такого исправления). Вся полезность высокоуровневых языков очевидна при сравнении, например, асма и тех же плюсов.

      Однако высокую квалификацию никто не отменял, без нее и грабли не классифицируешь и не уберешь их с дороги, чтобы другим больше не мешались.
  • +1
    В качестве примеров приведены банальные вещи. Хотя бы полгода промышленного программирования и будет очень сложно сделать ошибку во всех приведенных фрагментах кода. Конечно же от случайностей никто не застрахован, но cppcheck и valgrind всегда в помощь.
    Хотелось бы сказать кое-что о сообщениях об ошибках (ваш пример из статьи):
    gcc
    /usr/include/c++/4.8/bits/stl_algo.h:160: ошибка: no match for 'operator==' (operand types are 'std::vector<int>' and 'const int')
        if (*__first == __val)
                     ^

    clang
    /usr/include/c++/4.8/bits/stl_algo.h:160: ошибка: invalid operands to binary expression ('std::vector<int, std::allocator<int> >' and 'int')
              if (*__first == __val)
                  ~~~~~~~~ ^  ~~~~~

    Да, сообщение об ошибке сильно длиннее, но я привел первую значащую строчку. По мне так это ни чем не хуже того что говорит нам Rust.
    Кстати, если уж переписать код вашего примера на C++11, то он будет выглядеть не так уж и страшно:

        int a;
        std::vector<std::vector<int>> v;
        auto it = std::find(begin(v), end(v), a);
    • 0
      Примеры действительно банальны, и я это не скрываю. Зато их можно целиком взять и отдать Вашему компилятору, в том числе и online. Если Вы хотите погрузиться глубже в матрицу, посмотрите или почитайте выступление Niko Matsakis.

      > Конечно же от случайностей никто не застрахован, но cppcheck и valgrind всегда в помощь.
      Valgrind не доступен на Windows, а cppcheck не ловит все ошибки. В целом и то и другое я, несомненно, считаю необходимым для любого С++ проекта.

      Clang действительно даёт более красивые сообщения об ошибках, лучше предупреждает об угрозах, и достоен внимания. В конце концов, Clang, как и Rust (как и Swift), работает на LLVM.
      • +1
        Valgrind не доступен на Windows

        На Windows есть AppVerifier.
  • +3
    Не понимаю, почему комментаторы из лагеря C++ реагируют так, как будто их лично задевает, что Раст как язык лучше, чем плюсы. Ну лучше он, лучше. За собственную профессиональную ценность можете не беспокоиться, проектов на плюсах еще вашим внукам хватит (к сожалению).
    • +1
      Кажется, мои статьи дёргают за живое, заставляют некоторых людей выйти из своей пещеры. Пещеры иллюзии того, что нужно выбирать между надёжностью и производительностью, и что С++ занимает локальный оптимум в плане второго. Rust нарушает эти неписаные правила, и чтобы осознать это, нужно согласиться принять красную таблетку, а не синюю.
      • +3
        А потом бегать как белка в колесе за изменениями синтаксиса и стандартной библиотеки. Ржавчина еще весьма нестабильна. D в этом смысле уже чуть повзрослее.
    • +2
      А я вот не понимаю, почему вы считаете, что нас это задевает?
      • +2
        Не вы конкретно, но аж 5 комментаторов выше в разной форме сослались на «прямоту рук», требуемую для разработки на плюсах, в чем читается "kvark, ну раз ниасилил плюсы, не мозоль тут нам глаза".
        • 0
          Блин, да сколько ж вас в придуманном мире живет?

          Цитирую автора: «однако мне будет интересно почитать Ваше экспертное мнение в комментариях. „

          • 0
            Это не экспертное мнение, а завуалированный переход на личности. Повторюсь, к вам это не относится, ваши комментарии строго по делу.
        • 0
          Я не то подразумевал, я говорил что статья несёт в себе очень мало полезной информации. А не то, что Rust уг, C++ forever. Rust мне самому крайне интересен.
  • 0
    Шаблоны в Rust проверяются на корректность до их инстанцирования...

    В С++ тоже. Это называется two-phase name lookup. Не смотря на то, что Visual Studio не поддерживает его, он все еще остается требованием стандарта.
    womble.decadent.org.uk/c++/template-faq.html#two-phase
    • 0
      Спасибо, это ценное дополнение. Однако всё, что связано с аргументами шаблона, проверяется только на второй фазе (при инстацировании), так что смысл сравнения остаётся прежним.
      • +1
        Вот ежели наконец родят концепты, то можно будет все проверять как следует.
      • 0
        Я не спорю с тем, что диагностика в С++ может проигрывать другим языкам. Однако это
        всё, что связано с аргументами шаблона, проверяется только на второй фазе

        не совсем так. Скажем, частично проверка аргументов производится при специализации, и в некоторых других случаях.
        template <typename T1, typename T2>
        struct A
        {
        };
        template <typename T1>
        struct A<T1, 2>
        {
        };
        

        Ну да ладно, собственно это не важно. Rust хороший язык, надеюсь найдется время познакомиться с ним поближе.
    • 0
      Visual Studio не поддерживает

      Поправлюсь, не поддерживала до версии 2012, насколько я могу судить. Однако все еще работает не корректно в некоторых случаях.
      Например в таком
      template<typename T>
      struct Base
      {
          template<typename U>
          struct InnerBase
          {};
      };
      template<typename T, typename U>
      struct Derived : public Base<T>
      {
          struct InnerDerived : public Base<T>::template InnerBase<U>
          {};
      };
      
      int main()
      {
          Derived<int, int>::InnerDerived foo;
      }
      

      Сорри за оффтоп :)
  • +1
    Помедетировав над постом и комментариями автора пришел к выводу, что главный посыл автор, что Rust сообщает об ошибках на этапе компиляции.
    Но приведенные примеры, строго говоря дики для плюсовика. Любой из нас нарвавшись на ошибки связанные с указателями пару раз понимает всю остроту этой бритвы и начинает пользоваться с осторожностью.
    Единственно реально опасный случай может возникнуть с мнопоточностью, и то, только в ходе рефакторинга, в противном случае будет использован shared_ptr и атомические операции. Но честно, у меня не хватает воображения представить подобную ситуацию, т.е. ценность подобного предупреждения не велика.

    Главная опасность в указателях C++ заключается в их арифметике и reinterept_cast, но отказ от этого я уже прочувствовал на C# и мне провал по скорости и костылеписанию жутко не понравился
    • +2
      Я, кстати, сталкивался с ситуацией, когда программа, переписанная с обычных указателей на auto_ptr и еще что-то (программа не моя), потеряла 20% производительности. Правда падать перестала, что существенно важнее :-).
    • +5
      Да. Но Вы же не пишете все компоненты большой системы единолично. За себя Вы можете отвечать, а за соседа? А за человека, который работает вместе с Вами над Open Source проектом (Rust же в Mozilla изобретали)? Rust позволяет гарантировать безопасность в этих случаях. И это имеет качественные последствия. Например, Вам больше может быть не нужна аппаратная изоляция процессов (виртуальная память). Можно работать без неё, на более простых, дешёвых и экономичных процессорах. При этом с хорошим уровнем безопасности и с богатой функциональностью: http сервер от автора A не испортит данные графическому интерфейсу от автора B, которые друг друга в глаза не видели, и код у них закрытый. Актуально для интернета вещей.
      • –2
        Сто-стоп-стоп. Для интернет-вещей есть Java, C#, PHP. Область C\С++ — прикладное ПО, вычисления, высоконагрузочные проекты, системное ПО.
        • 0
          Вы правда не видите разницы между «интернетом вещей» и «интернет-вещами»?
          • –1
            По-моему первое в контексте беседы не имеет смысла. В интернете вещей процессы изолированны аппаратно изначально.
            • 0
              Вы отрицаете необходимость запуска нескольких процессов на одном устройстве?..
              • –1
                А в этом суть концепции?
                • 0
                  Нет, концепция интернета вещей заключается не в однозадачности устройств.
                  • –1
                    Да, и из этого следует трудности интеграции всех этих устройств. Rust предлагает что-то для решения этой проблемы? — нет.
                    • 0
                      Простите, но вы читать умеете?
                      Rust позволяет гарантировать безопасность в этих случаях. И это имеет качественные последствия. Например, Вам больше может быть не нужна аппаратная изоляция процессов (виртуальная память). Можно работать без неё, на более простых, дешёвых и экономичных процессорах.

                      Все еще не ясна связь Rust и интернета вещей?
                      • –1
                        Знаете, я не могу вам ответить, меня распирает смех и горькие слезы.
                        Прошу, просветите меня, зачем виртуализация была нужна для интернета вещей до появления раст?
  • 0
    Добавлю, что rust ещё не готов для серьёзного использования. Язык сильно меняется, старый код, как правило, не собирается новой версией компилятора. Компилятор всё ещё нестабилен, несколько раз встречался с тем, что код падает на ровном месте в segfault.

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