Ну и до кучи, маленькая задачка. Хочу иметь ключ в хэш-таблице из пары строк. Ключ хранит строки (т.е использует String). При поиске у меня есть пара строковый слайсов.
Поехали, три варианта, выбирайте на свой вкус (предлагайте свой, лучше!):
use std::mem;
use std::borrow::{Borrow, Cow};
use std::collections::HashSet;
use std::hash::{Hash, Hasher};
fn size_of_key<T>(_set: &HashSet<T>) -> usize {
mem::size_of::<T>()
}
fn main() {
// Extra string copying on lookups :(
let mut set = HashSet::new();
set.insert(("hello".to_string(), "bye".to_string()));
let flag = set.contains(&("hello".to_string(), "bye".to_string()));
println!("{}, key size {}", flag, size_of_key(&set));
// Extra memory used for Cow :(
let mut set = HashSet::new();
set.insert((Cow::Owned("hello".to_string()), Cow::Owned("bye".to_string())));
let flag = set.contains(&(Cow::Borrowed("hello"), Cow::Borrowed("bye")));
println!("{}, key size {}", flag, size_of_key(&set));
// Whaaat? BUT:
// No string copying! Key is small! Dynamic dispatch, though :(
#[derive(PartialEq, Eq, Hash)]
struct OwnedPair(String, String);
#[derive(PartialEq, Eq, Hash)]
struct BorrowedPair<'a, 'b>(&'a str, &'b str);
trait KeyPair {
fn pair(&self) -> (&str, &str);
}
impl KeyPair for OwnedPair {
fn pair(&self) -> (&str, &str) {
(&self.0, &self.1)
}
}
impl<'a, 'b> KeyPair for BorrowedPair<'a, 'b> {
fn pair(&self) -> (&str, &str) {
(self.0, self.1)
}
}
impl<'a> Borrow<KeyPair + 'a> for OwnedPair {
fn borrow(&self) -> &(KeyPair + 'a) {
self
}
}
impl<'a> Hash for (KeyPair + 'a) {
fn hash<H: Hasher>(&self, state: &mut H) {
self.pair().hash(state);
}
}
impl<'a> PartialEq for (KeyPair + 'a) {
fn eq(&self, other: &Self) -> bool {
self.pair() == other.pair()
}
}
impl<'a> Eq for (KeyPair + 'a) {}
let mut set = HashSet::new();
set.insert(OwnedPair("hello".to_string(), "bye".to_string()));
let flag = set.contains(&BorrowedPair("hello", "bye") as &KeyPair);
println!("{}, key size {}", flag, size_of_key(&set));
}
Не очень понимаю, откуда такая резкая реакция, но с точки зрения прикладного программиста разница между &str, String и Cow имеет малое значение. И то, и то — «строки». Разница в основном в способе владения (и в изменяемости, конечно, но в прикладном коде строка после создания изменяется редко [1]).
Те, кто всё ещё не верят, предлагаю прочитать статью (ну или попробовать что-то большое написать, впрочем YMMV [2]).
Это проявляется везде. Конвертация String в &str (обычно достаточно &my_string, но в каких-то случаях — нет, надо делать .as_str()). Конвертация Option в Option<&str> (ну-ка, быстро, как это делается?). Конвертация &str и String в Cow (и обратно). Создание String из литералов (при том, что это ещё и лишнее копирование — но вроде оптимизация на подходе).
Возможно, конечно, мы просто сильно заморачиваемся и можно было бы просто фигачить String.
[1]. YMMV, в принципе, в Rust можно более безопасно изменять String благодаря строгому borrow checker-у.
[2]. Можно, например, вообще не заморачиваться и просто орудовать String, зависит, от задачи.
Мы пишем на Rust систему, которую традиционно бы писали на Java (условный «кровавый энтерпрайз»). После пары месяцев активного написания, когда основные моменты Rust сходу понимаешь, каких-то особых трудностей нет.
Приходится больше думать, например, о владении объектов (в Java можно практически вообще не думать), но в целом чувствую себя вполне продуктивным. И уж Java-то по количеству кода грех не уделать — в Rust более мощная система построения абстракций (типажи, макросы, аттрибуты, более мощные параметры типов).
Но синтаксиса разного много, это да. Не вижу серьёзной проблемы с точки зрения долгосрочной коммерческой разработки — надо три месяца, будут у тебя три месяца на освоение. Это марафон, а не спринт.
А попробуй взять, да открыть проект на Rust (с Cargo.toml) в CLion + Rust plugin.
В IntelliJ IDEA Community можно просто открыть директорию с Rust-проектом и всё работает автоматически.
CLion открывать отказывается, его нужно тщательно уговаривать (кажется, import from sources работает). И даже после уговоров всё равно разные «хвосты» вылазят, типа ошибок unknown module: JAVA_MODULE, невозможности просто сделать compile, и автоматически сгенерированный CMakeLists.txt (который не нужен).
Вот сейчас у меня все файлы в CLion серенькие. Я так думаю, потому что он считает, что они не принадлежат проекту.
Забавно, у меня с некоторыми моими проектами строго наоборот. Так как из трёх составляющих проекта (ресурсы, сроки и качества) в моих хобби-проектах остаётся только качество (ресурсы и сроки, можно сказать, бесконечны), то остаётся только напирать на качество. Просто нравится делать всё «правильно» и аккуратно.
>Вопрос, кто должен за это платить (компания рабочим временем, или программист личным)?
У меня к этому такое отношение — компания платит мне не за часы, и даже не за результат, а за вклад в компанию.
А поэтому, я считаю вправе тратить время на то, что я посчитаю нужным, а компания — платить столько, сколько считает нужным за мой вклад.
Главное, чтобы ожидания с обеих сторон выполнялись.
При этом, понятно, что есть тёмная сторона, можно соответствовать ожиданиям делая примерно нулевой вклад (чем, на мой взгляд, занято далеко ненулевое количество сотрудников), но тут уж только личные моральные устои помогут.
С STM32 ситуация такая, что там программно особо ничего не сделаешь с дребезгом, если подсчёт через таймер делать. Я смог 100% побороть только аппаратно, через фильтр + триггер Шмитта.
Этот вариант никак не учитыват, что бывают ещё альтернативные порты. Например, USART3_TX может быть как PB10, так и PD8 (STM32F103).
Плюс, всё равно где-то (кроме спецификации) всё равно должна быть зафиксирована привязка к реальным номерам портов, чтобы удобнее было отслеживать соответствие кода и разрабатываемой аппаратной части.
Это для демо-платы пойдёт подход «а сконфигурируй мне USART3 и пофиг где — я потом куда надо провода воткну», а для аппаратной части, сделанной под задачу, важно знать этот пин. Причём, диктовать пин могут обе стороны — программной части надо, чтобы пин бы первым каналом «продвинутого» таймера TIM1 или TIM2 (их таких будет 4 варианта, а если соглашаться на инвертированный канал, то и все 8 вариантов), а аппаратной части какой-то пин может оказаться удобнее, чем другой.
В общем, моя мысль в том, что код настраивающий/работающий с периферией не должен быть «умным», так как проблема находится на более высоком уровне. Код не может знать, какой вариант был выбран «основной пин»/«второй пин». Не может знать, какой именно таймер был выбран «TIM1»/«TIM2». Не может знать, какой подвариант был выбран «CH1»/«CH1N». Поэтому, код должен просто принимать россыпь констант (ссылка на пин, на таймер, на конкретный USART) и предполагать, что эти выданные ему константы соотносятся правильным образом.
А потом, можно сделать какой-нибудь решатель, который бы верифицировал спецификацию (или выдавал разные опции) в зависимости от заданных ограничений и выдавал значения констант. За неимением такого решателя, пусть это будет просто конфигурационный файл с константами, который проверяет человек по спецификации.
Оптимизироваться не будут операции с промежуточным записями в регистр (потому что регистры — это volatile память). А цепочки типа
tim2.ccer.write(|w| w
.cc1p().set_bit()
.cc1e().set_bit());
без проблем соптимизируются в одну запись константы в регистр (потому что запись делается один раз в конце ccer_write, а установка битов делается в лямбда-функции).
Думаю, в C++ аналогичный код будет ровно так же соптимизирован.
Поехали, три варианта, выбирайте на свой вкус (предлагайте свой, лучше!):
Те, кто всё ещё не верят, предлагаю прочитать статью (ну или попробовать что-то большое написать, впрочем YMMV [2]).
Это проявляется везде. Конвертация String в &str (обычно достаточно &my_string, но в каких-то случаях — нет, надо делать .as_str()). Конвертация Option в Option<&str> (ну-ка, быстро, как это делается?). Конвертация &str и String в Cow (и обратно). Создание String из литералов (при том, что это ещё и лишнее копирование — но вроде оптимизация на подходе).
Возможно, конечно, мы просто сильно заморачиваемся и можно было бы просто фигачить String.
[1]. YMMV, в принципе, в Rust можно более безопасно изменять String благодаря строгому borrow checker-у.
[2]. Можно, например, вообще не заморачиваться и просто орудовать String, зависит, от задачи.
Приходится больше думать, например, о владении объектов (в Java можно практически вообще не думать), но в целом чувствую себя вполне продуктивным. И уж Java-то по количеству кода грех не уделать — в Rust более мощная система построения абстракций (типажи, макросы, аттрибуты, более мощные параметры типов).
Но синтаксиса разного много, это да. Не вижу серьёзной проблемы с точки зрения долгосрочной коммерческой разработки — надо три месяца, будут у тебя три месяца на освоение. Это марафон, а не спринт.
Я бы перевёл так:
Из всего вышесказанного следует, что персистентные коллекции в Rust не обязательно должны иметь
тот же самыйдругой интерфейс, что и обычные.>Из всего вышесказанного следует, что персистентные коллекции в Rust не обязательно должны иметь тот же самый интерфейс, что и обычные.
Оригинал:
>This implies to me that persistent collections in Rust don’t necessarily want to have a “different interface” than ordinary ones.
Как раз смысл статьи в том, что персистентные коллекции в Rust могут иметь тот же интерфейс, что и обычные, благодаря системе владения.
Уменьшил мою экзистенциальную тревогу по поводу того, что я хочу персистентный вариант этого Value. Ведь он у меня уже есть, просто clone дорогой. :)
P.S. А, ну и mem::forget больше не unsafe, мотивы см. leaking
>Было бы классно, если бы CLion ещё и с Rust плагином хорошо дружил (а не как сейчас, с разными приседаниями).
;)
В IntelliJ IDEA Community можно просто открыть директорию с Rust-проектом и всё работает автоматически.
CLion открывать отказывается, его нужно тщательно уговаривать (кажется, import from sources работает). И даже после уговоров всё равно разные «хвосты» вылазят, типа ошибок unknown module: JAVA_MODULE, невозможности просто сделать compile, и автоматически сгенерированный CMakeLists.txt (который не нужен).
Вот сейчас у меня все файлы в CLion серенькие. Я так думаю, потому что он считает, что они не принадлежат проекту.
Или даже коммерческая IDE для Rust с запчастями от CLion (типа того же дебаггера и вот этих вот ваших специфичных фич для embedded).
У меня к этому такое отношение — компания платит мне не за часы, и даже не за результат, а за вклад в компанию.
А поэтому, я считаю вправе тратить время на то, что я посчитаю нужным, а компания — платить столько, сколько считает нужным за мой вклад.
Главное, чтобы ожидания с обеих сторон выполнялись.
При этом, понятно, что есть тёмная сторона, можно соответствовать ожиданиям делая примерно нулевой вклад (чем, на мой взгляд, занято далеко ненулевое количество сотрудников), но тут уж только личные моральные устои помогут.
Хотя, конечно, Rust-овские lifetime тоже не ягодки. В плане использования ещё не сильно сложно, а вот в плане правильного проектирования — сложно.
Плюс, всё равно где-то (кроме спецификации) всё равно должна быть зафиксирована привязка к реальным номерам портов, чтобы удобнее было отслеживать соответствие кода и разрабатываемой аппаратной части.
Это для демо-платы пойдёт подход «а сконфигурируй мне USART3 и пофиг где — я потом куда надо провода воткну», а для аппаратной части, сделанной под задачу, важно знать этот пин. Причём, диктовать пин могут обе стороны — программной части надо, чтобы пин бы первым каналом «продвинутого» таймера TIM1 или TIM2 (их таких будет 4 варианта, а если соглашаться на инвертированный канал, то и все 8 вариантов), а аппаратной части какой-то пин может оказаться удобнее, чем другой.
В общем, моя мысль в том, что код настраивающий/работающий с периферией не должен быть «умным», так как проблема находится на более высоком уровне. Код не может знать, какой вариант был выбран «основной пин»/«второй пин». Не может знать, какой именно таймер был выбран «TIM1»/«TIM2». Не может знать, какой подвариант был выбран «CH1»/«CH1N». Поэтому, код должен просто принимать россыпь констант (ссылка на пин, на таймер, на конкретный USART) и предполагать, что эти выданные ему константы соотносятся правильным образом.
А потом, можно сделать какой-нибудь решатель, который бы верифицировал спецификацию (или выдавал разные опции) в зависимости от заданных ограничений и выдавал значения констант. За неимением такого решателя, пусть это будет просто конфигурационный файл с константами, который проверяет человек по спецификации.
без проблем соптимизируются в одну запись константы в регистр (потому что запись делается один раз в конце ccer_write, а установка битов делается в лямбда-функции).
Думаю, в C++ аналогичный код будет ровно так же соптимизирован.