Random Rust Dev
365 subscribers
73 photos
41 videos
1 file
28 links
Разработка на языке Rust.
Пишу простыни со своими мыслями о Rust и проектах на нем.
Download Telegram
Ищу интересные магические предметы для персонажей в ДнД.
Вижу "Сфера разрыва донжона".
Ну-ка, ну-ка, как оно там донжоны разрывает?

Donjon’s Sundering Sphere
А я надеялся
😁1
А вот интересно. Есть ли достаточно черная магия, которая позволит написать такие две функции.

const fn IsCalledWith<T>() -> bool { ... }
fn CallItWith<T>() { .. }


заметьте, что const.

По сути я хочу знать, а функция с неким типом вообще где-то в коде вызывается?
И получить const bool
This media is not supported in your browser
VIEW IN TELEGRAM
Давненько ничего я не выкладывал.
Поэтому вот вам видео с геометрической либой.

Первая операция - отражение.

Планируется полный комплект геометрической алгебры для 2д и 3д.

Тулза на коленке сделана для отладки.
🔥8
This media is not supported in your browser
VIEW IN TELEGRAM
Теперь можно отражать и точки и линии в линиях, а потом в отраженных отражать
🔥2👍1
This media is not supported in your browser
VIEW IN TELEGRAM
Как долго написать еще и отражение точек и линий в точке?

Я его написал после того как отправил прошлое видео
1
This media is not supported in your browser
VIEW IN TELEGRAM
Умножаем две линии друг на друга и получаем Motor. Который вращает объекты вокруг точки пересечения линий на удвоенный угол между ними.

А что если они параллельные?
Вращение превращается в линейное движение на удвоенное расстояние между линиями. А формула все та же.
3
Кто угадает как вычисляется пересечение двух линий?
Завершаем базовые функции линией через 2 точки и проекцией точки на линию и линии на точку
This media is not supported in your browser
VIEW IN TELEGRAM
Обычное умножение линий дает мотор, который сдвигает на удвоенное растояние и поворачивает на удвоенный угол между линиями.

Что ж, гораздо удобнее брать от этого умножения квадратный корень.

Теперь, как вы видите, точки следуют за второй линией в той же конфигурации, в какой они находятся у первой линии.
Начинаю регулярную рубрику Rust Tips 🔧

Сегодня - о замечательной функции Cell::from_mut

Она позволяет превратить &mut T в &Cell<T>, что все еще позволяет изменять T, но ее разрешается расшарить
Вот классический пример, где как новичики, так и ветераны спотыкаются о borrow checker

let mut counter = 0;

let mut inc = || counter += 1;
let print = || println!("{}", counter); // cannot borrow `counter` as immutable because it is also borrowed as mutable

inc();
inc();
print();


Передать &mut в inc и одновременно & в print нельзя.

Здесь мы можем использовать магию Cell::from_mut, не изменяя окружающий код.
Обернем ссылку на counter и раздадим в замыкания.


let mut counter = 0;
let cell = Cell::from_mut(&mut counter);

let inc = || cell.set(cell.get() + 1);
let print = || println!("{}", cell.get());

inc();
inc();
print();


🎉 И вуаля - всё работает! 🎉

Бонус для внимательных:
Теперь inc теперь реализует и Fn.
Так что можно тот же трюк использовать что бы передать замыкание, которое что-то мутирует в функцию, которая принимает Fn без Send.
👍195
Сегодняшний Rust Tip об очень полезной функции - Clone::clone_from.

Когда у вас уже есть объект и нужно заменить его клоном другого значения - не спешите писать a = b.clone().
Вместо этого используйте: a.clone_from(&b).

Такой вызов позволяет переиспользовать существующее значение - особенно ресурсы вроде памяти.
Это особенно эффективно при клонировании Box, Vec, String и прочих.

И, конечно, это работает транзитивно.
Вложенный Vec<T> будет клонировать T с помощью clone_from для существующих элементов.
При использовании #[derive(Clone)], компилятор автоматически вызывает clone_from для всех полей.
Но если вы реализуете Clone вручную - подумайте, не стоит ли также переопределить clone_from.
Ведь реализация по умолчанию просто делает *self = other.clone().
Если ваш тип не Copy, clone_from почти наверняка окажется дешевле.

Аналогичная оптимизация есть и у трейта ToOwned.
Для типов вроде str, Path или [T] используйте ToOwned::clone_into(&mut target) вместо target = value.to_owned().

Такие небольшие улучшения могут сэкономить вам годзиллионы процессорных циклов.
И при профилировании вы будете реже видеть, как ваше приложение тратит 90% времени на клонирование и выделение памяти.
👍161
Свежий Rust Tip по свежему stable API

С версии 1.86 в stable стал доступен метод slice::get_disjoint_mut.
Который позволяет получить мутабельные ссылки на разные элементы слайса одновременно.

Разность проверяется методом и при пересечении возвращается ошибка.
Равно как и при индексе вне диапазона.

Вот как его можно использовать

let mut array = [10, 20, 30, 40, 50];

let [a, b, c] = array.get_disjoint_mut([1, 3, 4]).expect("Index overlap");
*a += 100; // 20 -> 120
*b += 100; // 40 -> 140
*c += 100; // 50 -> 150


Так же как get, можно использовать Range, RangeInclusive, RangeFrom, RangeTo и RangeFull, хотя все кроме первых двух для этого метода вряд ли пригодятся.
Так как в метод передается массив, то все должны быть одного типа.


let [a, b, c] = array.get_disjoint_mut([0..2, 2..3, 3..5]).expect("Range overlap");
a.copy_from_slice(&[9, 10]);
b.copy_from_slice(&[11]);
c.copy_from_slice(&[12, 13]);


Похожим методом обзавелся и HashMap, но там вместо возвращения ошибки метод паникует, и возвращает массив Option.

let mut map = HashMap::from([
("a", 1),
("b", 2),
("c", 3),
]);

let [oa, ob] = map.get_disjoint_mut(["a", "b"]);

if let (Some(a), Some(b)) = (oa, ob) {
*a += 10;
*b += 20;
}



Для любителей unsafe есть версии *_unchecked этих методов, которые пропускают проверку непересечения индекстов/диапазонов/ключей.

Удачного вам мулти-индексирования.
🔥9👍3
Придумывал глупую шутку про программирование.
Варианты:

1. Добавил обработку краевых условий. Теперь не работает на нормальных.
2. Поймал исключение. До сих пор держу.
3. Поднял прод. Теперь он надо мной.
4. Вызвал функцию. Она не пришла.
5. Написал универсальную функцию. Не подошла ни к одному случаю.
6. Придумал абстракцию. Теперь боюсь к ней прикасаться.
7. Вынес повторяющийся код. Повторяется в другом месте.
8. Написал комментарий. Теперь только он и понятен.
🔥13
Rust Tip для тех кто отлаживает UB в своем unsafe коде в 2 часа ночи.

Делайте маленькие unsafe блоки. Стремитесь к одному выражению.
Всегда сопроводите комментарием, почему это safe.

Используйте линт, что б заставить себя писать unsafe блоки в unsafe функциях, если вы на старой версии компилятора.

Не пишите unsafe, если можете обойтись.
👍12
This media is not supported in your browser
VIEW IN TELEGRAM
Добрался сделать умножение моторов на скаляр. Что позволяет интерполировать и экстраполировать.
🔥3
Rust Tip дня: матчинг слайсов.

В Rust можно элегантно извлекать элементы из слайсов с помощью паттерн-матчинга:

match input {
[0xFF, rest @ ..] => println!("Starts with 0xFF, then {:?}", rest),
[first, second, .., last] => println!("Start: {first:#X}, {second:#X}, end: {last:#X}"),
[.., last] => println!("Ends with {last:#X}"),
[] => println!("Empty slice"),
}


Извлечение с одним вариантом.
if let [a, b, c] = input {
println!("Exactly three elements: {a}, {b}, {c}");
}


Извлечение с ранним возвратом при ошибке матчинга.

let [first, second, rest @ ..] = input else {
panic!("Expected at least two elements");
};
println!("Parsed: {first}, {second}, rest = {:?}", rest);


Сила pattern matching раскрывается в полной мере, когда вы:

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


Приятного вам матчинга.
👍202
Rust Tip для чернокнижников.

PhantomData<T> vs PhantomData<fn() -> T> - не одно и то же

PhantomData<T> указывает на семантику владения T.
PhantomData<fn() -> T> - лишь на то что где-то будет использоваться этот T.
Drop-check будет считать, что PhantomData<T> может вызывать Drop у T, а PhantomData<fn() -> T> - не будет.

Еще одно важное отличие это авто-трейты.

PhantomData<T> наследует их от T, а вот PhantomData<fn() -> T> нет.
Если T будет !Send или !Sync, то в первом случае и ваш тип будет. А в случае функции - нет. Указатели на функции Send, Sync, Unpin и прочие Freeze независимо от типов аргументов и возвращаемого типа.

#[derive(Default)]
struct Foo<T>(PhantomData<T>);

#[derive(Default)]
struct Bar<T>(PhantomData<fn() -> T>);

let foo: Foo<Rc<u8>> = Foo::default();
let bar: Bar<Rc<u8>> = Bar::default();

std::thread::spawn(|| { drop(foo); }); // error: `Rc<u8>` cannot be sent between threads safely
std::thread::spawn(|| { drop(bar); }); // success!


Следует учитывать, что PhantomData<fn(T)> переворачивает ковариантность, так что его стоит использовать когда именно это и нужно.

Удачной вам охоты на фантомные данные 👻
🔥17👍32
Канал перевалил за 300 подписчиков!!!

🎉 Ура-ура-ура 🎉
Please open Telegram to view this post
VIEW IN TELEGRAM
🔥24👍5🍾51
Rust Tip дня: #[inline(never)] и #[cold] - когда использовать?

🔹 #[inline(never)]

Изолирует редкий или громоздкий код от горячего пути.
Горячий путь меньше - лучше локальность - больше производительность.

Используй, если:

код вызывается редко.

хочется сократить размер inlined функций;

slow path.

fn some_threaded_algo(rw: &RwLock<Foo>) -> Bar {
let read = rw.read();
match try_do_stuff(&*read) { // may fail with only shared ref
Ok(bar) => bar,
Err(_) => {
drop(read);
slow_path(rw)
}
}

#[inline(never)]
fn slow_path(rw: &RwLock<Foo>) -> Bar {
let mut write = rw.write();
do_stuff(&mut *write) // may not fail with exclusive ref
}


🔹 #[cold]
Помечает функцию как исключительно редкую.
Позволяет релоцировать код для лучшей производительности горячего кода.

Используй, только если:

вызов происходит <1% случаев;

код отрабатывающий только на разогреве и не используемый позже.

функция вызывается только в исключительных случаях.

⚠️ Не ставь #[cold], если функция вызывается даже изредка - даже 1–2% уже много.
💡 Некоторые функции и макросы, такие как panic!(), сами по себе уже #[cold], дополнительная аннотация не требуется.

Для slow_path из примера выше можно добавить #[cold], если необходимость в экслюзивном локе это исключительная редкость.

Бонус.
Если вам привычны likely и unlikely, что бы помечать ветки как холодные, они есть в nightly.
На stable вы можете легко их реализовать сами - просто сделайте вызов пустой холодной функции.

#[inline(always)]
fn likely(b: bool) -> bool {
if b {
true
} else {
cold_path();
false
}
}


#[inline(always)]
fn unlikely(b: bool) -> bool {
if b {
cold_path();
true
} else {
false
}
}

#[inline(always)]
#[cold]
fn cold_path() {}
5👍2😱1💯1
Rust Tip мини

Остерегайтесь расставлять #[inline(always)].

#[inline] достаточно для того что бы компилятор заинлайнил функцию, если сочтет нужным (без него кросс библиотечные вызовы могут не инлайниться).

#[inline(always)] может привести к тому что код слишком разбухнет и нарушится локальность.

Дайте оптимизатору выполнять его работу.
🤝10💯1