LINUX.ORG.RU

Массивы в Rust

 


0

5

Продолжаю изучать Rust и вот решил попробовать в нём массивы.

Массивы в Rust неитерабельные, тоесть нельзя написать for item in array {...}. Вместо этого придётся написать либо for item in &array {...} либо for item in array.iter() {...} либо for item in array.to_vec() {...} либо что-то другое похожее, что создаёт из массива объект итерабельного типа. Ну или использовать цикл, который бежит по индексам for i in 0..array.len() { let n = array[i]; ...}. По моему это недостаток Rust. Для примера в Java for-each неявно разворачивается в одну из двух версий: с использованием индекса или с использованием итератора - в зависимости от того массив ли это или Iterable. Rust умеет делать лишь второй вариант, хотя это такой же синтаксический сахар, как и for-each в Java.

Так же, на сколько я понял, массив в Rust является не объектом, как в Java, а просто куском памяти с обвесами из макросов, как в C/C++. Таким образом функция не может получить, в качестве параметра, массив, длина которого неизвестна во время компиляции. Следующие три варианта функции не скомпилируются с тремя разными сообщениями об ошибке:

fn print_array1(array: [i32]) {
    //
}

fn print_array2(array: [i32; n], n: i32) {
    //
}

fn print_array3(n: i32, array: [i32; n]) {
    //
}

Варианты вроде fn print_array(array: [i32; 27]) {...} работать будут, но практической пользы не имеют. Значит придётся передавать не массив, а созданный из него итерабельный объект. Но это приводит к накладным расходам и с точки зрения системного программирования выглядит не очень хорошо. Может быть это ограничение снято в unsafe?

Ну хорошо, пусть речь идёт не о системном, а о прикладном программировании. Предположим, что я хочу пройтись по всем элементам двумерного массива, например, чтобы распечатать его в виде таблицы. В этом случае удобнее сразу пользоваться не массивом, а скажем слайсом (срезом), более похожим на массив в Java. В итоге у меня получился следующий код:
fn main() {
    let desk: &[&[i32]] = &[&[1, 2, 3, 4], &[5, 6, 7, 8], &[9, 10, 11, 12], &[13, 14, 15, 16]];

    print_desk(desk)
}

fn print_desk(desk: &[&[i32]]) {
    for line in desk {
        for cell in *line {
            print!("{:02} ", cell)
        }
        println!()
    }
}

Здесь у меня возникла проблема во внутреннем цикле. Я полагал, что типом переменной line является &[i32], но оказалост, что он &&[i32]. Поэтому во внутреннем цикле пришлось бежать не по line, а по *line. Другими вариантами были line.iter() или line.to_vec(). Почему здесь такой неконсистентный синтаксис? Так же, почему в сообщении об ошибке предлагается использовать более накладный line.iter() а не *line?

★★★★★

for item in &array {...}

нормальный синтаксис для языка с явными указателями

for item in array.to_vec() {...}

не надо так делать. to_vec(), я так понимаю, аллоцирует и копирует

fn print_array2(array: [i32; n], n: i32) {

я понимаю, что ты не знаешь, как что-то сделать. но зачем изобретать свой синтаксис и пытаться его скомпилировать?

fn print_array4(array: &[i32]) {
    println!("{}", array.len())
}
MyTrooName ★★★★★
()
Последнее исправление: MyTrooName (всего исправлений: 3)

let desk: &[&[i32]] = &[&[1, 2, 3, 4], &[5, 6, 7, 8], &[9, 10, 11, 12], &[13, 14, 15, 16]];

никакой это не двумерный массив

поищи крейты какие-нибудь

MyTrooName ★★★★★
()
Последнее исправление: MyTrooName (всего исправлений: 1)

Массивы в Rust неитерабельные ... По моему это недостаток Rust.

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

Так же, на сколько я понял, массив в Rust является не объектом, как в Java, а просто куском памяти с обвесами из макросов, как в C/C++. Таким образом функция не может получить, в качестве параметра, массив, длина которого неизвестна во время компиляции.

Длина массива по определению должна быть известна во время компиляции. Ты же внимательно читал главу про массивы? Иначе тебе нужен не массив, а вектор, например.

Virtuos86 ★★★★★
()

Следующие три варианта функции не скомпилируются с тремя разными сообщениями об ошибке:

fn print_array1(array: [i32]) {
    //
}

fn print_array2(array: [i32; n], n: i32) {
    //
}

fn print_array3(n: i32, array: [i32; n]) {
    //
}

))) Здесь n будет известно только в рантайме, при вызове print_array. Еще раз почитай пр массивы, их размер обязан быть известен на этапе компиляции.

Virtuos86 ★★★★★
()
Ответ на: комментарий от MyTrooName

for item in &array {...}

нормальный синтаксис для языка с явными указателями

for item in array.to_vec() {...}

не надо так делать. to_vec(), я так понимаю, аллоцирует и копирует


Разве &array ничего не аллоцирует? Это ведь новый объект, пусть, возможно, и ссылающийся на исходный массив.

fn print_array2(array: [i32; n], n: i32) {

я понимаю, что ты не знаешь, как что-то сделать. но зачем изобретать свой синтаксис и пытаться его скомпилировать?

fn print_array4(array: &[i32]) {
    println!("{}", array.len())
}


Ты невнимательно прочёл моё сообщение. Я знаю, что там можно и написал об этом:

Значит придётся передавать не массив, а созданный из него итерабельный объект.

bbk123 ★★★★★
() автор топика
Ответ на: комментарий от MyTrooName

никакой это не двумерный массив

Ты тоже читал невнимательно? Я же написал:

В этом случае удобнее сразу пользоваться не массивом, а скажем слайсом (срезом), более похожим на массив в Java.

Или &array - не слайс array?

bbk123 ★★★★★
() автор топика
Ответ на: комментарий от MyTrooName

с чего ты взял, что он более накладный?

line.iter() создаёт новый объект. Вот его код из стандартной библиотеки:

    #[ inline] <-- добавил пробел из-за бага в LOR-code
    fn iter(&self) -> Iter<T> {
        unsafe {
            let p = if mem::size_of::<T>() == 0 {
                1 as *const _
            } else {
                let p = self.as_ptr();
                assume(!p.is_null());
                p
            };

            Iter {
                ptr: p,
                end: slice_offset!(p, self.len() as isize),
                _marker: marker::PhantomData
            }
        }
    }

bbk123 ★★★★★
() автор топика

Здесь у меня возникла проблема во внутреннем цикле. Я полагал, что типом переменной line является &[i32], но оказалост, что он &&[i32].

У тебя desk это срез массива, содержащего срезы массивов, содержащих значения типа i32. ЕМНИП, в цикле будет неявно вызван метод iter среза, который возвращает Iterator, выдающий при каждой итерации ссылку на очередной элемент среза. Получается, выдаются &(&[i32]) или &&[i32]

Что-то даже заинтересовался вопросом). По памяти-то помню, почему, а почему точно — не помню.

Virtuos86 ★★★★★
()
Ответ на: комментарий от bbk123

А ты знаешь другой способ реализовать for_each? Тебе в любом случае нужно две вещи: указатель на начало и указатель на конец (ну или длину массива). От того что ты это назвал «объектом» вместо .begin() и .end() суть реализации не меняется.

Deleted
()
Ответ на: комментарий от Virtuos86

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

Ну конкретно этот недостаток очень легко исправить, не потеряв совместимость вниз. Про отвратность не согласен. Многое непривычно, это да. И сам язык ещё слишком молодой.

Длина массива по определению должна быть известна во время компиляции. Ты же внимательно читал главу про массивы? Иначе тебе нужен не массив, а вектор, например.

Я внимательно читал сообщения об ошибках, например такие:

19 | fn print_array(array: [i32]) {
   |                ^^^^^ `[i32]` does not have a constant size known at compile-time

Вектор конечно тоже подошёл бы, но если нет необходимости добавлять или удалять элементы, то лучше использовать массив. Даже такой как в Java - более дорогой, чем в C.

bbk123 ★★★★★
() автор топика
Ответ на: комментарий от Deleted

А ты знаешь другой способ реализовать for_each?

Такой, как в Java - с двумя вариантами реализации. Я же написал об этом.

bbk123 ★★★★★
() автор топика
Ответ на: комментарий от Virtuos86

Это не бага, в лоркоде есть такой тег [inlinе], поэтому лоркод и обработал декларацию как тег.

Это баг, поскольку он конфликтует с синтаксисом Rust, который поддерживается в [codе]. Можно добавить короткую форму, например [inl].

bbk123 ★★★★★
() автор топика
Ответ на: комментарий от bbk123

Такой, как в Java - с двумя вариантами реализации. Я же написал об этом.

Ох... Похоже ты сам не понимаешь как это в джаве «работает» дальше синтаксиса.

В общем, очень рекомендую для начала изучить C на нормальном уровне. Это поможет понять очень многие вещи и в расте и в джаве и в других ЯП.

Deleted
()
Ответ на: комментарий от bbk123

Я внимательно читал сообщения об ошибках, например такие:

Ты, конечно, можешь идти своим путем в изучении Rust, трудным и извилистым, но лучше читать учебники и документацию, а потом пробовать писать код.

Дарю ссылку: https://doc.rust-lang.org/std/. Ее и самому легко найти, разумеется, на странице документации на оф. сайте. Забиваешь в поисковую строку, например, array, и к твоим услугам исчерпывающая информация.

Virtuos86 ★★★★★
()
Ответ на: комментарий от Virtuos86

По моему ты просто цепляешься к словам. В сигнатуре функции fn print_array(array: [i32]) длина массива неизвестна. Я хотел сказать лишь об этом.

bbk123 ★★★★★
() автор топика
Ответ на: комментарий от bbk123

Это не бага, в лоркоде есть такой тег [inlinе], поэтому лоркод и обработал декларацию как тег.

Это баг, поскольку он конфликтует с синтаксисом Rust, который поддерживается в [codе]. Можно добавить короткую форму, например [inl].

А потом появится ЯП, в котором есть [inl]... Если сильно надо, можно обойти этот «баг»:

    #[​​inline]
    fn iter(&self) -> Iter<T> {
        unsafe {
            let p = if mem::size_of::<T>() == 0 {
                1 as *const _
            } else {
                let p = self.as_ptr();
                assume(!p.is_null());
                p
            };

            Iter {
                ptr: p,
                end: slice_offset!(p, self.len() as isize),
                _marker: marker::PhantomData
            }
        }
    }

Virtuos86 ★★★★★
()
Ответ на: комментарий от bbk123

Эта твоя функция — синтаксически некорректная запись. Я тебе хочу указать на это, раз ты сам не доходишь.

Primitive Type array

A fixed-size array, denoted [T; N], for the element type, T, and the non-negative compile-time constant size, N.

There are two syntactic forms for creating an array:

  • A list with each element, i.e. [x, y, z].
  • A repeat expression [x; N], which produces an array with N copies of x. The type of x must be Copy.

Обрати внимание на прописную N. Это означает, что оно должно быть целым числовым литералом.

Virtuos86 ★★★★★
()
Последнее исправление: Virtuos86 (всего исправлений: 1)
Ответ на: комментарий от Virtuos86

Здесь у меня возникла проблема во внутреннем цикле. Я полагал, что типом переменной line является &[i32], но оказалост, что он &&[i32].


У тебя desk это срез массива, содержащего срезы массивов, содержащих значения типа i32. ЕМНИП, в цикле будет неявно вызван метод iter среза, который возвращает Iterator, выдающий при каждой итерации ссылку на очередной элемент среза. Получается, выдаются &(&[i32]) или &&[i32]

Что-то даже заинтересовался вопросом). По памяти-то помню, почему, а почему точно — не помню.


Я знаю что из себя представляет desk и естественно понимаю, что тут используется итератор. Но программируя на Java я привык к тому, что iter.next() возвращает очередной элемент итерабельного объекта. Наверное эта привычка связана с тем, что в Java к любому объекту всегда обращаются только by reference, а в Rust (как и в C/C++) можно и by reference и by value. Видимо это объясняет почему для line нужно делать dereference. С другой стороны это неконсистентно с точки зрения читающего такой код человека.

bbk123 ★★★★★
() автор топика
Ответ на: комментарий от Virtuos86

Если сильно надо, можно обойти этот «баг»:

Ты лишь забыл рассказать как.

bbk123 ★★★★★
() автор топика
Ответ на: комментарий от Virtuos86

Ты продолжаешь цепляться к словам. Запись array: [i32] именно потому синтаксически некорректна, потому что во время компиляции неизвестна длина такого массива, тоесть неизвестна N. Именно об этом и было написано в сообщении об ошибке компиляции и именно об этом я говорил.

bbk123 ★★★★★
() автор топика
Ответ на: комментарий от bbk123

Сделать

fn print_desk(desk: &[&[i32]]) {
    for &line in desk {
        for &cell in line {
            print!("{:02} ", cell)
        }
        println!()
    }
}

И всё будет консистентно. Кроме того for _ in desk.iter() практические ничем не отличается от for _ in desk, так как это синтаксический сахар для for _ in desk.into_iter()

red75prim ★★★
()

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

Следовательно line.iter() никак не может быть «накладный» и почти всегда будет соптимизирован компилятором.

pftBest ★★★★
()
Ответ на: комментарий от Deleted

Ох... Похоже ты сам не понимаешь как это в джаве «работает» дальше синтаксиса.

Расскажи мне как это работает в Java. Мне очень интересно где я не прав.

bbk123 ★★★★★
() автор топика
Ответ на: комментарий от pftBest

Я знаю и что такое стек и что такое куча. Создание объекта где угодно требует ресурсов. Стек всего лишь автоматически освобождается после возврата из функции. Но это не означает, что стек у тебя резиновый и что на создание объекта на стеке ты не тратишь CPU.

bbk123 ★★★★★
() автор топика
Ответ на: комментарий от bbk123

Я знаю что из себя представляет desk и естественно понимаю, что тут используется итератор. Но программируя на Java я привык к тому, что iter.next() возвращает очередной элемент итерабельного объекта.

И в Rust тоже возвращается очередной элемент итерируемого объекта. Но итерируемым объектом в данном случае является итератор от исходного объекта - логично, правда? Если объект в цикле явным образом не приводится к итератору, то это происходит неявно - твой случай, и для этого, тип объекта должен соответствовать типажу IntoIterator. Поскольку, видимо, в Джаве ты не привык ходить по ссылкам на документацию, я это сделаю за тебя.

Итак, https://doc.rust-lang.org/std/primitive.slice.html - desk ведь срез?:

Primitive Type slice

A dynamically-sized view into a contiguous sequence, [T].

Slices are a view into a block of memory represented as a pointer and a length.

...

impl<'a, T> IntoIterator for &'a [T][src] type Item = &'a T

The type of the elements being iterated over.

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

А то, что ты привычки из Джавы тащишь в Раст, или ожидаешь, что Джава - ЯП со сборщиком мусора - будет взята эталоном в вопросах управления памятью для языка без сборщика мусора, немного необосновано.

Virtuos86 ★★★★★
()
Ответ на: комментарий от bbk123

Но это не означает, что стек у тебя резиновый и что на создание объекта на стеке ты не тратишь CPU.

О! Есть какой-то магический способ проитерироваться по массиву без использования дополнительных переменных? Делитесь скорее.

red75prim ★★★
()
Ответ на: комментарий от Deleted

В Java нет указателей, есть лишь ссылки. for-each в Java может работать и с массивами и с Iterable объектами, в число которых массивы не входят. for-each в Java - это всего лишь синтаксический сахар. В случае массива он разворачивается компилятором в другой цикл вида for (int i = 0; i < array.length; i++) { T n = array[i]; // остальной код }, а в случае Iterable он разворачивается в другой код вида for (Iterator<T> iter = iterable.iterator(); iter.hasNext();) {T n = iter.next(); // остальной код }. for-each в Rust - это, по сути, тот же самый синтаксический сахар, но почему-то поддерживающий лишь второй вариает и только лишь поэтому не поддерживающий массивы.

bbk123 ★★★★★
() автор топика
Ответ на: комментарий от bbk123

Ты продолжаешь цепляться к словам. Запись array: [i32] именно потому синтаксически некорректна, потому что во время компиляции неизвестна длина такого массива, тоесть неизвестна N. Именно об этом и было написано в сообщении об ошибке компиляции и именно об этом я говорил.

Компилятор умный, но дурак. Потому что по умолчанию полагает, что ему подают корректный код. Поэтому он и пытается найти смысл в том, что ты написал:

19 | fn print_array(array: [i32]) {
   |                ^^^^^ `[i32]` does not have a constant size known at compile-time

[i32] - это массив с длиной равной 1, содержащий тип i32. Если я напишу:

const CONST: [i32] = [i32];
одна из ошибок будет говорить то же самое. Поскольку массив по определению, которое ты так и не выучил, обязан иметь вычислимую на стадии компиляции длину, то и его элементы обязаны обладать этим свойством. Тип i32, очевидно, не обладает, то есть не реализует типаж std::marker::Sized. Поэтому компилятор честно ругается на первую глупость которую обнаружил в твоем коде. Оценить весь ее масштаб он, к сожалению, не в состоянии).

Virtuos86 ★★★★★
()
Последнее исправление: Virtuos86 (всего исправлений: 1)
Ответ на: комментарий от red75prim

О! Есть какой-то магический способ проитерироваться по массиву без использования дополнительных переменных? Делитесь скорее.

Поделись лучше каким образом Iter стал весить не больше, чем i32 и на его создание уходит меньше CPU, чем на создание i32.

bbk123 ★★★★★
() автор топика
Ответ на: комментарий от bbk123

Откуда один i32 взялся? Там два usize нужны: i и n. Тот же самый размер, что и у итератора, создающегося по .iter()

let n = array.len();
for i in 0..n { ... array[i] ... }

От n можно будет избавиться после появления const generics, но и реализация итератора для массивов тоже сможет этим воспользоваться, так что оверхеда всё-равно не будет.

red75prim ★★★
()
Ответ на: комментарий от bbk123

В Java нет указателей, есть лишь ссылки.

Пока код выполняется на реальном процессоре (а не сферическом в вакууме), в Java указатели есть. Просто JVM ловко их от тебя скрывает.

Deleted
()
Ответ на: комментарий от Virtuos86

[i32;1] - массив размера 1. [i32] - это DST (dynamically sized type), его размер хранится в ссылке на него (&[i32]), то есть [i32] сам по себе - массив неизвестного размера.

red75prim ★★★
()
Ответ на: комментарий от red75prim

Забавно. Я полагал, что компилятор рассматривает запись [i32] как форму инициализации массива с перечислением всех элементов массива типа [x, y, z]. Где об этом можно почитать?

Virtuos86 ★★★★★
()
Ответ на: комментарий от bbk123

Разве &array ничего не аллоцирует?

нет

аллокация - это malloc(), to_vec() по определению её производит. а &array - в машинных кодах вообще noop

наличие/отсутствие сахара для foreach на производительность машинного кода никак не влияет. а нет его в данном случае, вангую, потому что проблематично совместить вместе со всем остальным сахаром, который в этом контексте может быть (тот же Defer)

придётся передавать не массив, а созданный из него итерабельный объект.

&[i32] (слайс) - внутрях это указатель + длина. ровно то, что нужно, ни больше ни меньше. «объект» в расте никаких накладных расходов сверх своего содержимого не несет

я так понимаю, с СИ ты особо дела не имел?

MyTrooName ★★★★★
()
Ответ на: комментарий от bbk123

С другой стороны это неконсистентно с точки зрения читающего такой код человека.

соглашусь. сам до сих пор методом подбора звездочки расставляю. но я тоже нуб

MyTrooName ★★★★★
()
Ответ на: комментарий от Virtuos86

[i32] - это массив с длиной равной 1, содержащий тип i32.Если я напишу:

const CONST: [i32] = [i32];


одна из ошибок будет говорить то же самое.


Ни одна из ошибок не говорит о том, что [i32] - это массив с длиной равной 1

error[E0423]: expected value, found builtin type `i32`
 --> src\main.rs:2:27
  |
2 |     const CONST: [i32] = [i32];
  |                           ^^^ not a value

error[E0277]: the trait bound `[i32]: std::marker::Sized` is not satisfied
 --> src\main.rs:2:26
  |
2 |     const CONST: [i32] = [i32];
  |                          ^^^^^ `[i32]` does not have a constant size known at compile-time
  |
  = help: the trait `std::marker::Sized` is not implemented for `[i32]`
  = note: constant expressions must have a statically known size

error: aborting due to 2 previous errors


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

Ты так стараешься меня задеть, что сам не замечаешь, какую глупость говоришь. Я прекрасно знаю, про требование вычислимости длины массива на этапе компиляции в Rust и неоднократно об этом говорил. Элементы данного массива - это числа типа i32 и обладать этим же свойством они никак не могут.

Поэтому компилятор честно ругается на первую глупость которую обнаружил в твоем коде. Оценить весь ее масштаб он, к сожалению, не в состоянии

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

bbk123 ★★★★★
() автор топика
Ответ на: комментарий от red75prim

Откуда один i32 взялся? Там два usize нужны: i и n.

Это я по привычке из Java сказал. Там размер массива уже есть в самом массиве, как размер слайса в Rust. Тоесть в Java-вском for-each на стеке живёт лишь i. В любом случае Iter, создающийся в .iter() - это больше чем два числа и процесс его создания дороже создания i. Выше я скопировал код из стандартной библиотеки.

bbk123 ★★★★★
() автор топика
Ответ на: комментарий от bbk123

В отличие от джавы в расте есть оптимизатор, который выкинет весь этот код который ты нашел в iter. Он даже больше сделает, он вообще выкинет все переменные, так что на стеке ничего не будет, ни одного i32, все поместится в регистры.

pftBest ★★★★
()
Ответ на: комментарий от MyTrooName

&[i32] (слайс) - внутрях это указатель + длина. ровно то, что нужно, ни больше ни меньше.

Фактически тоже самое, что массив в Java. Но почему в случае массива в Rust [i32; N] длину, для итерации такого массива, нельзя брать из константы N, известной на этапе компиляции? Это ведь гораздо проще создания слайса.

наличие/отсутствие сахара для foreach на производительность машинного кода никак не влияет. а нет его в данном случае, вангую, потому что проблематично совместить вместе со всем остальным сахаром, который в этом контексте может быть (тот же Defer)

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

я так понимаю, с СИ ты особо дела не имел?

Имел, но довольно давно и не очень долго.

bbk123 ★★★★★
() автор топика
Ответ на: комментарий от red75prim

О... пропустил это сообщение.

Сделать

fn print_desk(desk: &[&[i32]]) {
    for &line in desk {
        for &cell in line {
            print!("{:02} ", cell)
        }
        println!()
    }
}

И всё будет консистентно.


Спасибо. Правда если &cell заменить на просто cell, тоже будет работать. В итоге имеем целых три варианта одного и того же кода, не использующего явных вызовов iter() и тому подобного.

Кроме того for _ in desk.iter() практические ничем не отличается от for _ in desk, так как это синтаксический сахар для for _ in desk.into_iter()

Выходит .iter() даже лучше, поскольку inline, а .into_iter() не inline и просто вызывает .iter()

bbk123 ★★★★★
() автор топика

Смотрю на примеры синтаксиса Руст в первом посте - и ещё больше убеждаюсь в том, что этот синтаксис ужасен, и дай Бог, чтобы со временем можно было писать все системные вещи на прекрасном почти во всех отношениях языке Crystal. Ну в ж. хипстерскую ржавчину.

DRVTiny ★★★★★
()
Ответ на: комментарий от bbk123

Выходит .iter() даже лучше, поскольку inline, а .into_iter() не inline и просто вызывает .iter()

Не выходит. Вы асм выхлоп смотрите, а не теоретизируйте.

Правда если &cell заменить на просто cell, тоже будет работать.

Только для примитивов. Если у типа нет Copy - нужна ссылка.

В целом все ваши вопросы не имеют ничего общего с Rust, ибо C/C++ работают также.

RazrFalcon ★★★★★
()

for item in &array {...} либо for item in array.iter() {...} либо for item in array.to_vec() {...}

&array - это сахар для array.iter(). Читайте доку.

array.to_vec() - это вообще про другое.

RazrFalcon ★★★★★
()
Ответ на: комментарий от DRVTiny

По сути, оба этих языка представляют из себя не что иное, как хипстерскую кристальную ржавчину.

anonymous
()
Вы не можете добавлять комментарии в эту тему. Тема перемещена в архив.