Ответы пользователя по тегу Rust
  • Почему в Docker собирается не тот Rust?

    vabka
    @vabka Куратор тега Rust
    Как уже выше сказали - cargo не увидел, что src поменялись и не стал собирать заново.
    Определяет он, что файл изменился по дате изменения файла. Если дата изменения файла меньше либо равна дате компиляции, то повторной компиляции не будет.
    Чтобы обновить дату изменения файла - достаточно сделать touch main.rs после первой сборки.
    В docker появился docker init, который сам делает корректный dockerfile - так что в принципе уже и не нужно руками dockerfile писать.
    Генерирует он примерно такое для компиляции (зачем так - описано в комментариях):
    ARG RUST_VERSION=1.71.0
    ARG APP_NAME=project
    FROM rust:${RUST_VERSION}-slim-bullseye AS build
    ARG APP_NAME
    WORKDIR /app
    
    # Build the application.
    # Leverage a cache mount to /usr/local/cargo/registry/
    # for downloaded dependencies and a cache mount to /app/target/ for
    # compiled dependencies which will speed up subsequent builds.
    # Leverage a bind mount to the src directory to avoid having to copy the
    # source code into the container. Once built, copy the executable to an
    # output directory before the cache mounted /app/target is unmounted.
    RUN --mount=type=bind,source=src,target=src \
        --mount=type=bind,source=Cargo.toml,target=Cargo.toml \
        --mount=type=bind,source=Cargo.lock,target=Cargo.lock \
        --mount=type=cache,target=/app/target/ \
        --mount=type=cache,target=/usr/local/cargo/registry/ \
        <<EOF
    set -e
    cargo build --locked --release
    cp ./target/release/$APP_NAME /bin/server
    EOF


    Ещё можно попробовать написать так (этим пользовался я раньше):
    # FROM, Установка rust и прочих зависимостей
    # ...
    
    RUN cargo new /app
    COPY app/Cargo.toml /app/
    
    # This step compiles only our dependencies and saves them in a layer. This is the most impactful time savings
    # Note the use of --mount=type=cache. On subsequent runs, we'll have the crates already downloaded
    WORKDIR /app
    RUN --mount=type=cache,target=/usr/local/cargo/registry cargo build --release
    
    COPY ./app /app
    
    # touch нужен, чтобы изменить дату изменения файла
    RUN --mount=type=cache,target=/usr/local/cargo/registry \
      set -e; \
      touch /app/src/main.rs; \
      cargo build --release;


    Оба варианта позволяют активно использовать фичи кэширования в Docker, чтобы не выкачивать все зависимости и не перекомпилировать их при каждой сборке образа.
    Ответ написан
    Комментировать
  • Как отсортировать в алфавитном порядке элементы внутри объекта типа JsonValue?

    vabka
    @vabka Куратор тега Rust
    Можно вот так попробовать, но советую уйти от крейта vkapi, тк он даже нормально не типизирован и нужно работать с json-ами, будто на js.

    let get_albums_response = api.request("video.getAlbums", &mut params).await.unwrap();
    let mut items: Vec<&JsonValue> = get_albums_response["response"]["items"].members().collect();
    items.sort_by(|a, b| a["title"].as_str().cmp(&b["title"].as_str()));

    Нужный результат будет лежать в переменной items.

    В этом плане vkclient будет гораздо удачнее, тк позволяет работать с serde.
    https://crates.io/crates/vkclient

    Да и даже она не очень много бенефитов даёт и я бы навелосипедил вокруг reqwest
    Ответ написан
    Комментировать
  • Как изменить структуру кода?

    vabka
    @vabka Куратор тега Rust
    Нет, нельзя. Модули в Rust оперируют функциями/типами/трейтами, но не отдельными кусками кода.
    Ты можешь вынести функции f1 и f2 в отдельные файлы:

    main.rs
    mod m01;
    mod m02;
    mod m03;
    
    fn main() {
        m01::f1();
        m02::f2();
        let (i, p) = m03::f3();
        println!("i = {:?}", i);
        println!("p = {:?}", p);
    }


    m01.rs
    pub fn f1() {
     let num: u8 = 12;
     println!("num = {:?}", num);
    }


    m03.rs
    pub fn f2() {
     let s: String = "abc".to_string();
     println!("s = {:?}", s);
    }


    В случае m03 ты можешь попробовать сделать функцию, которая будет возвращать значения переменных i и p:
    pub fn f3() -> (u8, String) {
      let i: u8 = 88;
      let p: String = "xyz".to_string();
      (i, p)
    }


    PS: Я уже было хотел предложить макрос вида:
    macro_rules! f4 {
      () => {
        let i: u8 = 88;
        let p: String = "xyz".to_string();
      }
    }

    Но он работать не будет, ибо гигиена.

    Но можно сделать так:
    macro_rules! f4 {
      ($a: ident, $b: ident) => {
        let $a: u8 = 88;
        let $b: String = "xyz".to_string();
      }
    }
    
    fn main() {
        f4!(i, p);
        println!("{} {}", i, p);
    }


    PPS: ну и ещё есть макрос include!, который буквально решает твою задачу - вставить кусок кода из файла, но его я не советую использовать.
    Ответ написан
    2 комментария
  • Как сделать много вставок в HashMap за минимальное время?

    vabka
    @vabka Куратор тега Rust
    Отвечая на твои вопросы:
    1 - есть ли что-то быстрее, чем tokio
    2 - правильно ли я использую tokio
    3 - насколько хорошая с точки зрения производительности идея использовать Arc
    4 - можно ли ускорить саму по себе структуру HashMap или только переписывать?


    1. В твоём случае лучше взять rayon для параллельной обработки, тк tokio предназначен для асинхронного io.
    2. см п1. Как именно ты tokio использовал я не смотрел. Дмитрий Беляев хорошо ответил по этому поводу
    3. Плохая. Лучше взять другую структуру данных
    4а. Можно процентов на 30 ускорить HashMap если заранее сделать with_capacity
    4б. И в n раз ускорить если сделать несколько HashMap по одному для каждого из N потоков (и передать во владение каждому потоку, чтобы не тратиться на синхронизацию и подсчёт ссылок).
    Для большого количества вставок неизвестного количества данных лучше подойдёт BTreeMap

    1. Многопоток тебе тут не поможет (а нет, обманул. Многопоток поможет. А вот async-нет)
    2. Ты бенчмаркаешь format!("{}", i)
    3. Вообще тебе тут стоит посмотреть на какие-нибудь concurrency-safe lockfree структуры. Например есть достаточно популярный крейт dashmap который такое предлагает.
    UPD: я обманул сам себя. dashmap не lockfree. Под капотом это как раз несколько HashMap, спрятанных за RwLock:
    pub struct DashMap

    pub struct DashMap<K, V, S = RandomState> {
        shift: usize,
        shards: Box<[RwLock<HashMap<K, V, S>>]>,
        hasher: S,
    }


    Мои бенчмарки с использованием criterion

    Результат:
    Обрати внимание, что тесты без format на порядок быстрее проходят.
    Но я не уверен, что корректно написал бенчмарк для btree_known_key__3M
    hashmap_no_capacity_format_key__3M
                            time:   [1.4810 s 1.5362 s 1.5952 s]
    
    hashmap_set_capacity_format_key__3M
                            time:   [1.0688 s 1.0744 s 1.0804 s]
    
    btree_format_key__3M    time:   [754.93 ms 843.10 ms 933.95 ms]
    
    
    vec_set_apacity__3M     time:   [1.7122 ms 1.7309 ms 1.7655 ms]
    
    dashmap_rayon_format_key__3M
                            time:   [294.76 ms 303.70 ms 316.85 ms]
    
    btree_known_key__3M     time:   [554.56 ms 556.18 ms 558.41 ms]


    Код

    use std::{
        collections::{BTreeMap, HashMap},
        time::Instant,
    };
    
    use criterion::{black_box, criterion_group, criterion_main, Criterion};
    
    fn hashmap_no_capacity_format_key(n: usize) -> HashMap<String, usize> {
        let mut map = HashMap::new();
        for i in 0..n {
            let key = format!("key_{i}");
            map.insert(key, i);
        }
        map
    }
    
    fn hashmap_set_capacity_format_key(n: usize) -> HashMap<String, usize> {
        let mut map = HashMap::with_capacity(n + 1);
        for i in 0..n {
            let key = format!("key_{i}");
            map.insert(key, i);
        }
        map
    }
    
    fn btreemap_format_key(n: usize) -> BTreeMap<String, usize> {
        let mut map = BTreeMap::new();
        for i in 0..n {
            let key = format!("key_{i}");
            map.insert(key, i);
        }
        map
    }
    fn vec_set_capacity(n: usize) -> Vec<usize> {
        let mut vector = Vec::with_capacity(n);
        for i in 0..n {
            vector.push(i);
        }
        vector
    }
    
    fn btreemap_known_key(keys: impl Iterator<Item = (String, usize)>) -> usize {
        let mut map = BTreeMap::new();
        for (k, v) in keys {
            map.insert(k, v);
        }
        map.len()
    }
    
    fn dashmap_rayon_format_key(n: usize) -> dashmap::DashMap<String, usize> {
        use rayon::prelude::*;
        let map = dashmap::DashMap::with_capacity(n);
        (0..n).into_par_iter().for_each(|i| {
            let key = format!("key_{i}");
            map.insert(key, i);
        });
        map
    }
    fn bench(c: &mut Criterion) {
        c.bench_function("hashmap_no_capacity_format_key__3M", |b| {
            b.iter(|| hashmap_no_capacity_format_key(black_box(3_000_000)))
        });
        c.bench_function("hashmap_set_capacity_format_key__3M", |b| {
            b.iter(|| hashmap_set_capacity_format_key(black_box(3_000_000)))
        });
        c.bench_function("btree_format_key__3M", |b| {
            b.iter(|| btreemap_format_key(black_box(3_000_000)))
        });
        c.bench_function("vec_set_apacity__3M", |b| {
            b.iter(|| vec_set_capacity(black_box(3_000_000)))
        });
        c.bench_function("dashmap_rayon_format_key__3M", |b| {
            b.iter(|| dashmap_rayon_format_key(black_box(3_000_000)))
        });
        c.bench_function("btree_known_key__3M", |b| {
            b.iter_custom(|times| {
                let mut total = vec![];
    
                for _ in 0..times {
                    let mut keys = Vec::with_capacity(3_000_000);
                    for i in 0..3_000_000 {
                        keys.push((format!("key_{i}"), i));
                    }
                    let start = Instant::now();
                    black_box(btreemap_known_key(black_box(keys.drain(..))));
                    total.push(start.elapsed());
                }
                total.iter().sum()
            });
        });
    }
    criterion_group! {
        name = benches;
        config = Criterion::default().sample_size(10);
        targets = bench
    }
    criterion_main!(benches);


    Ответ написан
    5 комментариев
  • Как в rust конвертировать place expression в value expressions?

    vabka
    @vabka Куратор тега Rust
    Попробуй так
    fn main() {
        let funcs = {
            // Можно использовать не FnOnce, а что-то другое. Например Fn или FnMut. Но в любом случае придётся использовать dyn, тк наша лямбда берёт что-то из окружающего контекста.
    
            let mut funcs: Vec<Box<dyn FnOnce() -> usize>> = Vec::with_capacity(3);
    
            for i in 0..3 {
                let i_copy = i.clone(); // вообще clone() тут не нужен, тк usize реализует трейт Copy. Оставлено для примера
                funcs.push(Box::new(move || i_copy));
            }
            funcs
        };
        
        for func in funcs {
            let result = func();
            println!("{result}");
        }
    }

    https://play.rust-lang.org/?version=stable&mode=de...

    Другой вариант - делать не массив лямбд, а сделать структуру с методом и сделать массив структур.
    В твоём случае это будет эффективнее:
    #[derive(Copy, Clone, Debug)]
    struct Something(usize);
    impl Something {
      fn value(&self) -> usize {
        self.0
      }
    }
    
    fn main() {
        let items = {
            let mut items = Vec::with_capacity(3);
    
            for i in 0..3 {
                items.push(Something(i));
            }
            items
        };
        
        for item in items {
            let result = item.value();
            println!("{result}");
        }
    }

    https://play.rust-lang.org/?version=stable&mode=de...
    Ответ написан
    Комментировать
  • Как переустановить Rust?

    vabka
    @vabka Куратор тега Rust
    У тебя нет никакой ошибки. Просто промотай консоль чуть ниже.
    То сообщение, которое тебе он вывел - он выводит всегда при запуске.
    Вот полный текст (я выделил курсором то, что ты прикрепил в вопросе):
    64a72702f13f6822200744.png
    Если у тебя по какой-то причине rustup закрывается и не даёт сделать выбор - передай ему все нужные параметры в аргументах:
    .\rustup-init.exe -y --default-host x86_64-pc-windows-msvc --default-toolchain stable --profile default


    Оригинальный ответ
    Вариант раз:
    1. Удали переменную окружения RUSTUP_HOME, CARGO_HOME и удали C:\Users\Eugene\.cargo\bin из PATH, да и саму папку .cargo тоже.
    2. Перезагрузи компьютер
    3. Снова запусти rustup

    Вариант два:
    Ещё можешь попробовать запустить rustup, предварительно выставив переменную среды RUSTUP_INIT_SKIP_EXISTENCE_CHECKS=yes
    Ответ написан
    8 комментариев
  • Как собрать rust проект оффлайн?

    vabka
    @vabka Куратор тега Rust
    Добавь опцию --offline
    cargo build --offline
    Эта опция гарантирует, что при сборке cargo не полезет в интернет.

    Предварительно тебе нужно загрузить все зависимости при помощи cargo fetch или cargo vendor

    https://doc.rust-lang.org/cargo

    UPD: Раз не понял, то вот полный рецепт, как использовать cargo vendor:
    1. Создал проект через cargo init
    2. Добавил зависимость через cargo add rand
    3. Включил вендоринг через cargo vendor (с доступом в интернет)
    4. Добавил файл .cargo/config.toml с содержимым
    [source.crates-io]
    replace-with = "vendored-sources"
    
    [source.vendored-sources]
    directory = "vendor"

    Так меня попросил сделать cargo после вызова cargo vendor

    5. Пишу код...
    fn main() {
        let x: i32 = rand::random();
        println!("{}", x);
    }

    6. Собираю cargo build --release --offline (без доступа в интернет)

    Сборка успешно проходит, никаких обращений в интернет нет.

    При этом папку с проектом я могу легко перенести на другой компьютер и также собрать без интернета.
    Ответ написан
    Комментировать
  • Как правильно добавить взаимодействие с сервисом на Rust через MassTransit?

    vabka
    @vabka Куратор тега Rust
    Вроде пишут, что формат сообщений предопределён:
    https://stackoverflow.com/questions/49558358/masst...

    Раз так, то берёшь serde, amqprs и пишешь
    Ответ написан
  • Есть ли статьи, которые приводят наглядные примеры того, как код на rust превосходит код на других языках?

    vabka
    @vabka Куратор тега Rust

    Особенно там, где был использован язык Си или С++

    (если исключить memory safety и fearless concurrency)
    1. Хороших плюсовиков найти всё сложнее, ибо молодые разработчики часто хотят что-то более современное/простое/приятное.
    2. Переход с какого-нибудь более высокоуровнего языка на Rust гораздо легче, чем на C++
    3. DX у Rust на порядо лучше.
    4. Код на Rust на порядок более выразительный, чем код на Си

    За счёт этого поддержка кодовой базы на Rust заметно дешевле выходит

    Например вот что Тинькофф пишет:

    Наш Процессинговый Центр занимается разработкой финансовых систем, критичных к даунтайму и времени обработки. Изначально мы делали все свои продукты либо на чистом Си, либо на плюсах (C++14), однако пару лет назад мы переписали большой кусок нашего бэкенда на Rust, и нам настолько понравилось, что теперь все наши новые процессинговые сервисы пишутся на нём.



    Мне бы хотелось видеть какое-то сравнение, что вот так стало сильно лучше и безопаснее, а вот было так написано изначально на оригинальном языке

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


    ну тут все-равно unsafe

    В проектах на Rust он явный и от него можно избавиться, завернув в безопасную обёртку, которая будет гарантировать корректную работу с памятью и ffi.
    В проектах на C++ у тебя по факту всё является unsafe.

    ну, нам еще нужен подсчет ссылок

    В плюсах тоже активно пользуются подсчётом ссылок и всякими умными указателями, если по коду не очевидно, когда можно будет освободить память
    Ответ написан
    6 комментариев
  • Как в этом сниппете кода работает владение (Rust)?

    vabka
    @vabka Куратор тега Rust
    Не может одновременно существовать mutable borrow и immutable borrow.
    Замыкание берёт mutable borrow и пока замыкание находится в области видимости - никто не может читать значение переменной.

    Мне кажется, лучше чем компилятор это не объяснит:
    Compiling playground v0.0.1 (/playground)
    error[E0502]: cannot borrow `counter` as immutable because it is also borrowed as mutable
      --> src/main.rs:11:30
       |
    7  |     let mut increase = || {
       |                        -- mutable borrow occurs here
    8  |         counter += INCR;
       |         ------- first borrow occurs due to use of `counter` in closure
    ...
    11 |     println!("counter = {}", counter);
       |                              ^^^^^^^ immutable borrow occurs here
    ...
    14 |         increase();
       |         -------- mutable borrow later used here
       |
       = note: this error originates in the macro `$crate::format_args_nl` which comes from the expansion of the macro `println` (in Nightly builds, run with -Z macro-backtrace for more info)
    
    error[E0502]: cannot borrow `counter` as immutable because it is also borrowed as mutable
      --> src/main.rs:16:34
       |
    7  |     let mut increase = || {
       |                        -- mutable borrow occurs here
    8  |         counter += INCR;
       |         ------- first borrow occurs due to use of `counter` in closure
    ...
    14 |         increase();
       |         -------- mutable borrow later used here
    15 |
    16 |         println!("counter = {}", counter);
       |                                  ^^^^^^^ immutable borrow occurs here
       |
       = note: this error originates in the macro `$crate::format_args_nl` which comes from the expansion of the macro `println` (in Nightly builds, run with -Z macro-backtrace for more info)
    
    error[E0503]: cannot use `counter` because it was mutably borrowed
      --> src/main.rs:18:12
       |
    7  |     let mut increase = || {
       |                        -- `counter` is borrowed here
    8  |         counter += INCR;
       |         ------- borrow occurs due to use of `counter` in closure
    ...
    14 |         increase();
       |         -------- borrow later used here
    ...
    18 |         if counter == STOP {
       |            ^^^^^^^ use of borrowed `counter`
    
    Some errors have detailed explanations: E0502, E0503.
    For more information about an error, try `rustc --explain E0502`.
    error: could not compile `playground` due to 3 previous errors
    Ответ написан
    6 комментариев
  • В чем суть Cell и RefCell?

    vabka
    @vabka Куратор тега Rust
    Там где тебя, например, трейт, или логика обязывает использовать &self, но тебе нужно поменять состояние.
    Ответ написан
    Комментировать
  • Почему трейт не может быть результатом метода внутри трейта?

    vabka
    @vabka Куратор тега Rust
    В первом случае:
    fn chto_to(&self) -> impl StringAnalyzer;
    impl Trait значит, что функция возвращает неизвестную структуру, которая реализует трейт.
    Возвращаемое значение определяется компилятором в момент объявления функции.
    Внутри трейтов он это не может (хотя вроде хотят в будущем разрешить)

    Во втором случае:
    fn lexing<T>(&self) -> T where T: StringAnalyzer;
    Тут возвращаемое значение определяется по месту вызова.

    Если ты хочешь impl Trait использовать в трейтах - ты можешь использовать associated type:
    pub trait NewTrait {
      type AssociatedType: ExistingTrait;
      fn function() -> Self::AssociatedType;
    }
    Ответ написан
    1 комментарий
  • Что не хватает в системе для кросс компиляции под aarch64 linux?

    vabka
    @vabka Куратор тега Rust
    tl;dr; rustflags трогать не нужно. Нужно указать пути к тулзам (линкеру и сишным/плюсовым компиляторам) для билда под aarch64

    Нагуглил https://users.rust-lang.org/t/cross-compilation-us...
    Делаю как предлагают (у меня Ubuntu правда, а не дебиан и не арч) - у меня сработало
    rustup update stable
    mkdir cross_comp
    cd cross_comp
    cargo init --bin
    cargo target add aarch64-unknown-linux-gnu
    
    sudo apt install g++-aarch64-linux-gnu libc6-dev-arm64-cross
    
    CARGO_TARGET_AARCH64_UNKNOWN_LINUX_GNU_LINKER=aarch64-linux-gnu-gcc \
    CC_aarch64_unknown_linux_gnu=aarch64-linux-gnu-gcc \
    CXX_aarch64_unknown_linux_gnu=aarch64-linux-gnu-g++ \
    cargo build --target=aarch64-unknown-linux-gnu

    rustup кстати не нужно было трогать, о чём сам растап говорит, при попытке установить тулчейн для другой архитектуры.
    Неудачная попытка

    Попробовал так:
    rustup update stable
    mkdir cross_comp
    cd cross_comp
    cargo init --bin
    cargo target add aarch64-unknown-linux-gnu

    Получил такую же ошибку.
    Ответ написан
    2 комментария
  • Как преобразовать ошибку потока Rust в строку?

    vabka
    @vabka Куратор тега Rust
    1. У Any есть метод is, с помощью которого можно проверить тип. В твоём случае это &'static str (собственно то, что ты в panic и засунул)
    2. =>
    Err(err) => {
                println!("{}", err.is::<&'static str>());
                let value = err.downcast::<&'static str>().unwrap();
                println!("{}", value); // Выводит строку failed to spawn
            }
    Ответ написан
    Комментировать
  • Где можно безопасно хранить токены в десктопе?

    vabka
    @vabka Куратор тега Rust
    В случае маков можно хранить в keychain.
    https://developer.apple.com/documentation/security...

    В случае некоторых дистров линукса - тоже , но надо смотреть для каждого.

    В случае винды - можно использовать DPAPI
    https://learn.microsoft.com/en-us/previous-version...

    Во всех случаях можно хранить не сами токены, а ключи для расшифровки, а сами токены держать зашифрованными в файлах

    UPD: на винде есть ещё https://learn.microsoft.com/ru-ru/windows/uwp/secu...
    А на линуксе есть libsecret

    UPD2: есть готовый Крейт, который это всё многообразие обобщает: keyring
    Ответ написан
    4 комментария
  • Как правильно обработать ошибку?

    vabka
    @vabka Куратор тега Rust

    Или возвращатьBoxплохая идея, а нужно засовывать другие ошибки внутрь своего кастомного типа?

    Ага. Много где в библиотеках внутренние ошибки заворачиваются/конвертируются в кастомный тип. Как раз по причине того что боксить - не бесплатно, а dyn - это не удобно для потребителя.

    В принципе можно, в теории dyn распаковать, но это антипаттерн
    Ответ написан
    Комментировать
  • Что использовать для отрисовки 3d графики на Rust?

    vabka
    @vabka Куратор тега Rust
    Самое модное и молодёжное - wgpu
    Умеет работать с Vulkan, Metal, OpenGL, DirectX, WebGL. На главной странице есть и примеры приложений

    Ещё, возможно, тебя заинтересует naga, как дополнение к wgpu - это либа для трансляции шейдеров.

    Ещё, возможно, тебя заинтересует bevy
    - это ECS-фреймворк для разработки игр и не только. В него встроена поддержка wgpu
    Ответ написан
    2 комментария
  • Можно ли избежать такой конструкции?

    vabka
    @vabka Куратор тега Rust
    Чтобы было чуть проще, тебе нужно реализовать оператор для:
    1. m + m
    2. &m + &m
    3. m + &m
    4. &m + m

    https://stackoverflow.com/questions/38811387/how-t...

    Но совсем избавиться от & не получится - компилятор в любом случае должен знать, когда ты хочешь копирование/move, а когда ты хочешь заимствовать.
    Ответ написан
    Комментировать