Мне в идее больше всего нравится как раз работа с конфликтами. В идее самый удобный three-way-merge с которым я когда-либо работал (плюс ещё интеграция с подсветкой/анализами для кода).
Там, где я сейчас работаю — уже нет, в связи со спецификой архитектуры и технологий. А вот там, где я был раньше, модульности очень сильно не хватало. Понятно, что одним лишь изменением в языке всё не исчерпывается, здесь нужна инфраструктура — библиотеки, сервера приложений и т. д., но даже изменение в языке было бы очень приятным.
Новые API годные (особенно встроенный JSON), но в целом релиз, судя по всему, будет довольно унылым. Одну из самых ожидаемых фич — Jigsaw — в девятку не только не включили, но и вообще, собственно модульность для конечных пользователей JDK отложена на неопределённый срок. Печально :(
Ну loop это по сути то же самое что while true в других языках — просто сокращение (которое ещё может учитываться компилятором для оптимизации). Кроме того, из-за паттерн-матчинга очень часто я замечаю за собой применение паттерна типа
loop {
match do_something {
p1 if a => { ... }
p2 if b => { ... }
something_else => { ...; break }
}
}
Это другое, чем то, что вы описали. Здесь мутабельность результата вычисления зависит от мутабельности исходной коллекции. И здесь да, придётся код повторять дважды, к сожалению, потому что в Rust нельзя параметризовать функции по мутабельности.
Однако этот юзкейс очень ограниченный. find и find_mut и аналогичные методы, возвращающие ссылки на внутренние элементы структуры данных — это чуть ли ни единственное, что страдает в этом случае. Более того, я бы сказал, что это специфично для обобщённых коллекций вроде того TreeMap; для собственных структур данных эта проблема будет возникать ещё реже.
Например, сейчас если у вас есть функция, которая принимает параметром коллекцию и не меняет ее (например, считает что-то на основе данных в коллекции), то в Rust вам придется написать две версии такой функции: одну для immutable коллекции, и одну — для mutable, — потому что так сейчас устроена система типов.
Вы что-то путаете. В Rust мутабельность определяется не структурой самой по себе, а тем, что с ней можно сделать и в каком слоте она лежит.
Если вы создаёте, например, коллекцию, и хотите, чтобы кто-то мог её изменить, вы пишете методы, которые меняют коллекцию, так, чтобы они принимали &mut self. Методы, которые не меняют коллекцию, будут принимать просто &self. После этого вы можете вызывать изменяющие и неизменяющие методы, если структура лежит в мутабельной переменной, и только неизменяющие, если структура лежит в немутабельной переменной. Поэтому если вы не собираетесь менять коллекцию, вам понадобится только одна функция, которая принимает &; если же вы хотите менять коллекцию, в функцию вам понадобится передавать &mut:
pub struct SomeCollection<T> {
// ...
}
impl<T: Copy> SomeCollection<T> {
fn get(&self, i: uint) -> T { ... }
fn set(&mut self, i: uint, v: T) { ... }
}
fn compute_something(coll: &SomeCollection<int>) -> int {
// здесь можно вызвать coll.get():
let x = coll.get(0);
// но вызвать coll.set() нельзя, будет ошибка компиляции
// coll.set(0, x+1);
x
}
fn compute_and_modify(coll: &mut SomeCollection<int>) -> int {
// здесь можно и то, и другое
let x = coll.get(0);
coll.set(0, x+1);
x
}
fn main() {
// s1 - неизменяемый слот (переменная)
let s1: SomeCollection<int> = SomeCollection { ... };
// можно вызвать compute_something(), но не compute_and_modify()
println!("{}", compute_something(&s1));
// println!("{}", compute_and_modify(&mut s1));
let mut s2 = s1; // перемещаем в мутабельный слот
// можно вызвать и то, и другое
println!("{}", compute_something(&s2));
println!("{}", compute_and_modify(&mut s2));
}
Поэтому я и сказал — в некотором смысле. А именно — модель обмена сообщениями у них различна.
Но в целом, конечно, и то и другое — это модели concurrency, и акторы в некотором роде находятся на более высоком уровне абстракции (действительно, акторов можно сделать через CSP, а наоборот — я сомневаюсь), поэтому различие довольно расплывчато.
На самом деле, акторная модель и модель CSP (которая как раз используется в Go) в некотором роде противоположны :)
В акторной модели основные объекты — это акторы, которые посылают друг другу сообщения с данными. Сообщения могут отправляться от любого актора любому, главное — знать его идентификатор. Акторы «работают» только тогда, когда обрабатывают очередное сообщение.
В CSP основной объект — это легковесный процесс. Процессы обмениваются данными с помощью каналов, которые заранее устанавливаются между теми процессами, которым нужно будет общаться. Данные пересылаются только через каналы, процессу неизвестно, что находится по другую его сторону. Процессы работают всегда, за исключением того времени, которое они проводят в ожидании сообщений из каналов.
На самом деле Optional в Java 8 был введён исключительно для поддержки коллекций. Его опасно использовать, например в JavaEE, в частности, потому что он не сериализуемый. С этим связана довольно напряжённая дискуссия (см. дальше в треде ответ Brian Goetz, блин, не знаю как его фамилию по-русски написать) в списке рассылки пару лет назад. Optional в Java 8 предназначается самими авторами языка исключительно для реализации идиомы «опциональное возвращаемое значение». Поэтому, как ни прискорбно, пользоваться Optional'ом из JDK8 далеко не всегда возможно. Одна надежда на авторов гуавы что они свой Optional улучшат, добавят наконец flatMap, например.
Поскольку Rust — системный язык, там есть «задний ход» в небезопасный низкоуровневый мир — unsafe, где возможны небезопасные операции, вроде разыменования сырых указателей. Если программист будет пользоваться этими блоками направо и налево, он получит фактически C, где возможно всё, в том числе расстрелять себе ноги урановыми медведями.
Но сам язык сделан так (и на это сильный упор в его документации), что эти блоки нужно использовать только тогда, когда это реально необходимо (взаимодействие с C, например, или реализация высокоуровневых абстракций вроде Rc). Вне этих блоков ошибки, связанные с неправильной работой с памятью, исключены (с поправкой на возможные ошибки компилятора и баги в библиотеках). Поэтому, хоть языки и не могут в принципе спасти от кривого кода, они очень сильно могут облегчить когнитивную нагрузку и исключить наиболее распространённые ошибка. Если бы это было не так — мы бы до сих пор писали бы на ассемблере.
Вы не сможете написать на Go, например, прошивку для микроконтроллера — рантайм слишком большой и ресурсоёмкий, а на Rust — вполне, если отключить его рантайм. В Go рантайм отключить хоть и можно, но бесполезно — вся безопасность работы с памятью, обеспеченная сборщиком мусора, потеряется. В Go также нет сырых указателей, которые очень важны для работы с железом. Да, там есть unsafe.Pointer, но работа с ними вызывает адскую боль. Это одно из объяснений.
Как ни странно, в этом желании разработчики вас поддерживают. Поэтому в языке закорючек стало гораздо меньше по сравнению с первой версией языка, как и сокращений. Например, ещё в начале года owned pointer записывался как ~T, например:
struct A {
x: ~int
}
теперь вместо тильды используется Box<T>, который, собственно, в статье используется. А в прошлом году из языка удалили @, который обозначал как раз указатель за сборщиком мусора.
А больше в языке ничего сверхкраткого с закорючками нет.
Ну схожесть разве что в стрелках в функциях и в statement as expression. Но последним обладают многие языки, та же Scala например.
И Rust, и D, и GO мощные и современные языки, которые лишены многих недостатков C++. Поэтому, думаю, что можно спокойно выбирать язык с тем синтаксисом, который больше нравится.
Вот здесь один из авторов языка (фактически, современную систему с &/&mut указателями и borrow checker'ом заложил именно он) рассказывает, как именно обеспечивается безопасность работы с памятью в Rust, и какого класса ошибки исключаются. Очень интересный доклад, интересующимся советую посмотреть.
Это первая из ссылок-источников в посте, просто хочу обратить на неё внимание.
list.add(list.size(), item)
.<source>
:loop
это по сути то же самое чтоwhile true
в других языках — просто сокращение (которое ещё может учитываться компилятором для оптимизации). Кроме того, из-за паттерн-матчинга очень часто я замечаю за собой применение паттерна типаили что-то подобное. Актуально для парсеров.
Я бы посоветовал переписать её с использованием Java 8 (вот здесь ещё информация). Код станет гораздо более красивым и понятным.
Однако этот юзкейс очень ограниченный.
find
иfind_mut
и аналогичные методы, возвращающие ссылки на внутренние элементы структуры данных — это чуть ли ни единственное, что страдает в этом случае. Более того, я бы сказал, что это специфично для обобщённых коллекций вроде того TreeMap; для собственных структур данных эта проблема будет возникать ещё реже.Вы что-то путаете. В Rust мутабельность определяется не структурой самой по себе, а тем, что с ней можно сделать и в каком слоте она лежит.
Если вы создаёте, например, коллекцию, и хотите, чтобы кто-то мог её изменить, вы пишете методы, которые меняют коллекцию, так, чтобы они принимали
&mut self
. Методы, которые не меняют коллекцию, будут принимать просто&self
. После этого вы можете вызывать изменяющие и неизменяющие методы, если структура лежит в мутабельной переменной, и только неизменяющие, если структура лежит в немутабельной переменной. Поэтому если вы не собираетесь менять коллекцию, вам понадобится только одна функция, которая принимает&
; если же вы хотите менять коллекцию, в функцию вам понадобится передавать&mut
:Но в целом, конечно, и то и другое — это модели concurrency, и акторы в некотором роде находятся на более высоком уровне абстракции (действительно, акторов можно сделать через CSP, а наоборот — я сомневаюсь), поэтому различие довольно расплывчато.
В акторной модели основные объекты — это акторы, которые посылают друг другу сообщения с данными. Сообщения могут отправляться от любого актора любому, главное — знать его идентификатор. Акторы «работают» только тогда, когда обрабатывают очередное сообщение.
В CSP основной объект — это легковесный процесс. Процессы обмениваются данными с помощью каналов, которые заранее устанавливаются между теми процессами, которым нужно будет общаться. Данные пересылаются только через каналы, процессу неизвестно, что находится по другую его сторону. Процессы работают всегда, за исключением того времени, которое они проводят в ожидании сообщений из каналов.
unsafe
, где возможны небезопасные операции, вроде разыменования сырых указателей. Если программист будет пользоваться этими блоками направо и налево, он получит фактически C, где возможно всё, в том числе расстрелять себе ноги урановыми медведями.Но сам язык сделан так (и на это сильный упор в его документации), что эти блоки нужно использовать только тогда, когда это реально необходимо (взаимодействие с C, например, или реализация высокоуровневых абстракций вроде Rc). Вне этих блоков ошибки, связанные с неправильной работой с памятью, исключены (с поправкой на возможные ошибки компилятора и баги в библиотеках). Поэтому, хоть языки и не могут в принципе спасти от кривого кода, они очень сильно могут облегчить когнитивную нагрузку и исключить наиболее распространённые ошибка. Если бы это было не так — мы бы до сих пор писали бы на ассемблере.
~T
, например:теперь вместо тильды используется
Box<T>
, который, собственно, в статье используется. А в прошлом году из языка удалили @, который обозначал как раз указатель за сборщиком мусора.А больше в языке ничего сверхкраткого с закорючками нет.
Полностью согласен.
Это первая из ссылок-источников в посте, просто хочу обратить на неё внимание.