Ответы пользователя по тегу Rust
  • Как работают пакеты и крейты?

    bingo347
    @bingo347
    Crazy on performance...
    Крэйт - это в первую очередь дерево модулей. Каждый крэйт содержит как минимум 1 корневой модуль (обычно это main.rs или lib.rs, но так же это могут быть модули доп бинарников, модули интеграционных тестов, модули примеров). Так же к крэйту относятся модули, которые объявили в других модулях этого крейта (ключевое слово mod).
    Помимо этого крэйт - это сущность которой оперирует компилятор rustc, крэйт является единицей компиляции, то есть в rustc на компиляцию попадает крэйт целиком (на вход подаём корневой модуль, а он уже сам бегает по всему дереву согласно объявлениям mod).

    Пакет - это сущность которой оперирует cargo. Компилятор rustc ничего не знает про пакеты. По простому пакет это папка с файлом Cargo.toml, в котором есть секция package (бывают ещё Cargo.toml объявляющие только workspace). Пакет состоит из крейтов, притом должен быть как минимум 1 крейт бинарника или библиотеки, а библиотечный крейт может быть только 1 или отсутствовать вовсе.
    Пакет - это то, что публикуется в registry (такие как crates.io).
    Так же в зависимостях мы указываем именно пакеты (но только те, что содержат крэйт-библиотеку).
    Так же именно пакеты указываются в команде cargo install, при этом будут собраны все бинарные крейты входящие в пакет, а получившиеся исполняемые файлы будут помещены .cargo/bin
    Ответ написан
    2 комментария
  • Как реализовать взятие текста из файла JSON, в Rust?

    bingo347
    @bingo347
    Crazy on performance...
    У Вас относительный путь, относительные пути зависят от контекста запуска, то есть считаются они относительно той папки из которой производится запуск.
    То есть данная программа будет искать разные файлы в зависимости от того из какой папки Вы её запустите.
    Ответ написан
  • Как открывать много TCP соединений и поддерживать их?

    bingo347
    @bingo347
    Crazy on performance...
    Как уже написали в соседнем ответе, лимит портов 65535 на 1 интерфейс.
    Но это на один интерфейс, самое простое решение тут воткнуть несколько сетевых карт, да у них будут разные ip (по сути у одной машины будет пул ip), но для 100к коннектов хватит 2 карточек.
    В Linux (насчет других ОС не уверен, но возможно тоже) есть способы поизвращаться и поднять 2+ виртуальных интерфейса на одной сетевой карте. При этом у машины так же будет 2 ip адреса и нужно будет распределять соединения между ними.

    Хотя по хорошему я бы посмотрел здесь в сторону UDP с одним занятым портом на машине, а необходимые возможности TCP уже воспроизводить программно.
    Ответ написан
    5 комментариев
  • Как правильно расположить config.toml?

    bingo347
    @bingo347
    Crazy on performance...
    Cargo считывает все конфигурационные файлы начиная с папки проекта (там где файл Cargo.lock и папка target) и во всех его родительских папках, а так же в домашней папке cargo.
    https://doc.rust-lang.org/cargo/reference/config.h...
    То есть в данном примере будут работать оба этих файла.

    Я бы проверял в сторону работает ли это условие:
    cfg(all(windows, target_env="msvc"))
    Ответ написан
    1 комментарий
  • Как избежать дубликации кода реализуя Trait'ы в Rust?

    bingo347
    @bingo347
    Crazy on performance...
    Написать derive макрос. Правда стоит соизмерить трудозатраты.
    Или написать обычный декларативный макрос (не так гибко как derive, но написать проще).

    А вообще, Rust не Java/C#, trait - не интерфейс.
    Если приведете не абстрактные примеры, вполне возможно, что задача решается проще.
    Ответ написан
    Комментировать
  • Проблема использования #[test_case] в rust?

    bingo347
    @bingo347
    Crazy on performance...
    Вам же явно написали:#![feature(custom_test_frameworks)]

    Атрибуты вида #[something] применяются к айтему под ними, атрибуты вида #![something] - к айтему в котором они находятся (должны находится в самом верху).
    https://doc.rust-lang.org/reference/attributes.html

    Ну и еще, feature работает только в nightly тулчейне.
    Ответ написан
    5 комментариев
  • Какой сайт с задачами rust?

    bingo347
    @bingo347
    Crazy on performance...
    Онлайн запускалка кода (плэйграунд): https://play.rust-lang.org
    Официальный список задач для тренировки от создателей языка: https://github.com/rust-lang/rustlings
    ну и + то что уже Василий Банников написал
    Ответ написан
    1 комментарий
  • Обязателен ли web-server (NGINX) для Actix Web?

    bingo347
    @bingo347
    Crazy on performance...
    Кэширование, сжатие, https все же лучше на nginx переложить
    Ответ написан
    Комментировать
  • Возможно ли использовать tauri-plugin-sql в главном коде rust?

    bingo347
    @bingo347
    Crazy on performance...
    Боюсь что только форкнуть и сделать вот эту структуру публичной:
    https://github.com/tauri-apps/tauri-plugin-sql/blo...

    Плюс не уверен как работают инжекты в tauri, возможно еще понадобится заменить инжекты на lazy_static
    Ответ написан
    Комментировать
  • Как исправить код конвертации vec_u8 to vec_&str?

    bingo347
    @bingo347
    Crazy on performance...
    Сумбурный вопрос, но если правильно понял, то автор хочет получить Vec<&str> элементы которого ссылаются на исходный вектор байт и в каждом элементе строка из 1 символа:
    let u01 = vec![59, 13, 10, 32, 47, 42];
    let u01_str = std::str::from_utf8(&u01).expect("invalid utf8");
    let mut u02 = Vec::with_capacity(u01.len());
    let mut i0 = 0;
    for (i, _) in u01_str.char_indices().skip(1) {
        u02.push(&u01_str[i0..i]);
        i0 = i;
    }
    u02.push(&u01_str[i0..]);
    println!("u02 = {:?}", u02);
    Ответ написан
    Комментировать
  • Как передать из функции значения в разные потоки?

    bingo347
    @bingo347
    Crazy on performance...
    Начну с того, что код представленный автором в комментах к вопросу имеет deadlock между мьютексом и recv() из канала и завершается лишь по тому, что мы не ждем фоновые потоки. Вариант без deadlock будет выглядеть так:
    fn test() {
        let mut channels = Arc::new(Mutex::new(Vec::with_capacity(PAR)));
        let mut joins = Vec::with_capacity(PAR);
        for _ in 0..N / PAR {
            for _ in 0..PAR {
                let mut channels = Arc::clone(&channels);
                joins.push(thread::spawn(move || {
                    get(channels.lock().unwrap());
                }));
            }
        }
        for j in joins {
            j.join().unwrap();
        }
    }
    
    #[inline(always)]
    fn get(mut channels: MutexGuard<Vec<mpsc::Sender<i32>>>) -> i32 {
        let (tx, rx) = mpsc::channel();
        channels.push(tx);
        if channels.len() == PAR {
            exec(channels);
        } else {
            drop(channels); // drop гварда отпускает мьютекс
        }
        rx.recv().unwrap()
    }
    
    #[inline(always)]
    fn exec(mut channels: MutexGuard<Vec<mpsc::Sender<i32>>>) {
        let mut i = 0;
        for c in channels.iter() {
            i += 1;
            c.send(1).unwrap();
        }
        println!("{}", i);
        channels.clear();
        // а здесь гвард дропнется сам
    }

    Вторая проблема в том, что все потоки выполняются по сути по очереди, так как ждут разблокировки мьютекса от других потоков, из-за чего многопоточка тут не дает никаких преимуществ, а лишь создает накладные расходы. Ради эксперимента я попробовал заменить мьютекс на еще один канал:
    fn test() {
        let (tx, rx) = mpsc::channel::<mpsc::Sender<i32>>();
        let mut handles = Vec::with_capacity(N + 1);
        handles.push(thread::spawn(move || exec(rx)));
        for _ in 0..N {
            let tx = tx.clone();
            handles.push(thread::spawn(move || {
                get(tx);
            }))
        }
        drop(tx);
        for handle in handles {
            handle.join().unwrap();
        }
    }
    
    fn get(sender: mpsc::Sender<mpsc::Sender<i32>>) -> i32 {
        let (tx, rx) = mpsc::channel();
        sender.send(tx).unwrap();
        rx.recv().unwrap()
    }
    
    fn exec(receiver: mpsc::Receiver<mpsc::Sender<i32>>) {
        let mut channels = Vec::with_capacity(PAR);
        loop {
            for _ in 0..PAR {
                let Ok(tx) = receiver.recv() else {
                    return;
                };
                channels.push(tx);
            }
            let mut i = 0;
            for c in channels.iter() {
                i += 1;
                c.send(1).unwrap();
            }
            println!("{}", i);
            channels.clear();
        }
    }
    Но особо это профита не дает, так как основной пожиратель перфоманса - switch context в ОС. Тысячи потоков делают только хуже. Запускать потоков сильно больше чем есть ядер - это вообще плохая идея. Просто ради интереса переписал еще раз на асинхронных каналах tokio:
    async fn test() {
        let (tx, rx) = mpsc::unbounded_channel::<mpsc::UnboundedSender<i32>>();
        let mut handles = Vec::with_capacity(N + 1);
        handles.push(tokio::spawn(async move { exec(rx).await }));
        for _ in 0..N {
            let tx = tx.clone();
            handles.push(tokio::spawn(async move {
                get(tx).await;
            }))
        }
        drop(tx);
        for handle in handles {
            handle.await.unwrap();
        }
    }
    
    async fn get(sender: mpsc::UnboundedSender<mpsc::UnboundedSender<i32>>) -> i32 {
        let (tx, mut rx) = mpsc::unbounded_channel();
        sender.send(tx).unwrap();
        rx.recv().await.unwrap()
    }
    
    async fn exec(mut receiver: mpsc::UnboundedReceiver<mpsc::UnboundedSender<i32>>) {
        let mut channels = Vec::with_capacity(PAR);
        loop {
            for _ in 0..PAR {
                let Some(tx) = receiver.recv().await else {
                    return;
                };
                channels.push(tx);
            }
            let mut i = 0;
            for c in channels.iter() {
                i += 1;
                c.send(1).unwrap();
            }
            println!("{}", i);
            channels.clear();
        }
    }
    и запустил в многопоточном рантайме в дефолтной конфигурации (количество воркеров == количеству ядер), работает быстрее в 19 раз.

    P.S. без I/O операций асинхронщина тоже создает ненужные накладные расходы, я ее здесь использовал только из-за простоты переписывания, лучше взять обычный thread pool с синхронными тасками.
    Ответ написан
    Комментировать
  • Какой Object pool стоит использовать в Rust?

    bingo347
    @bingo347
    Crazy on performance...
    меня очень смущает постоянно выделять буффер

    Конкретно в данном примере буффер выделяется на стеке, то есть на его выделение не тратится ровным счетом ничего, так как стек и так уже выделен при запуске потока.
    Есть конечно копеечные затраты ресурсов на заполнение буфера нулями. И если в буффер гарантированно сначала идет запись, как в данном случае, то это в принципе можно обойти:
    use std::mem::MaybeUninit;
    let mut buffer = unsafe {
        #[allow(invalid_value)]
        MaybeUninit::<[_; 1024]>::uninit().assume_init()
    };
    Но я бы так не делал. Во-первых чистота кода не стоит этих копеек производительности, а во-вторых немного накосячите с чтением и будет UB.
    Ответ написан
    3 комментария
  • Почему в Docker собирается не тот Rust?

    bingo347
    @bingo347
    Crazy on performance...
    FROM rust:latest as build
    
    WORKDIR /test-tcp
    
    COPY ./Cargo.lock ./Cargo.lock
    COPY ./Cargo.toml ./Cargo.toml
    COPY ./src ./src
    
    RUN cargo build --release
    
    FROM debian:buster-slim
    
    COPY --from=build /test-tcp/target/release/test-tcp /usr/src/test-tcp
    
    CMD ["/usr/src/test-tcp"]

    Проблема в том, что в докере Вы создаете пустой проект из шаблона cargo
    RUN USER=root cargo new --bin test-tcp
    и компилируете его, потом копируете src
    Да, там есть еще один билд после этого, но я попробовал собрать Ваш Dockerfile у себя, и получил на втором билде результат инкрементальной сборки без изменений. То есть cargo не увидел, что папка src поменялась.
    Ответ написан
    4 комментария
  • Как правильно принимать данные в потоках?

    bingo347
    @bingo347
    Crazy on performance...
    Arc нужно клонировать до move в замыкание, которое запускается на потоке. Если данные используются только на чтение, то этого будет достаточно, если данные будут меняться из нескольких потоков, то следует дополнительно обернуть их в Mutex/RwLock (или из std::sync или из библиотеки parking_lot).
    use std::sync::Arc;
    
    fn main() {
        let data = Arc::new(vec![1, 2, 3]);
    
        let thread_1 = std::thread::spawn({
            let data = Arc::clone(&data);
            move || {
                println!("Thread 1 data: {:?}", data);
            }
        });
    
        let thread_2 = std::thread::spawn({
            let data = Arc::clone(&data);
            move || {
                println!("Thread 2 data: {:?}", data);
            }
        });
    
        thread_1.join().unwrap();
        thread_2.join().unwrap();
    }

    Передавать так можно хоть вектор, хоть свою структуру, главное чтоб у типа был трейт Send и лайфтайм 'static (все владеющие типы имеют такой лайфтайм).
    Так как передаем мы по сути Arc, то Send должен быть у него, а он будет для любого содержимого реализующего Sync.

    Я обернул создание отдельного потока в функцию и так передавал в поток данные. Удобно, что такую функцию могу вынести в отдельный файл-модуль. Но не смог такое сделать динамически в цикле для группы потоков. Хочу подойти к варианту, когда поток, который закончил выполнение своего кода (раньше других), можно опять запустить из main и передать ему новую задачу (новые данные), - структуру данных, которую привёл в основном вопросе.
    Если правильно понял, то Вам нужен thread pool. Можно использовать из библиотеки rayon: https://docs.rs/rayon/1.7.0/rayon/struct.ThreadPoo...
    Но если хочется повелосипедить, можно нечто такое сделать:
    use std::{
        sync::{
            mpsc::{self, Sender},
            Arc, Mutex,
        },
        thread::{self, JoinHandle},
    };
    
    type Task = Box<dyn FnOnce() + Send + 'static>;
    
    pub struct ThreadPool {
        handles: Vec<JoinHandle<()>>,
        task_sender: Sender<Task>,
    }
    
    impl ThreadPool {
        pub fn new() -> Self {
            let threads_count = thread::available_parallelism()
                .map(|n| n.get())
                .unwrap_or(2);
            let (task_sender, task_receiver) = mpsc::channel::<Task>();
            let task_receiver = Arc::new(Mutex::new(task_receiver));
            let mut handles = Vec::with_capacity(threads_count);
            for _ in 0..threads_count {
                let task_receiver = Arc::clone(&task_receiver);
                handles.push(thread::spawn(move || loop {
                    let task_receiver = task_receiver.lock().unwrap_or_else(|e| e.into_inner());
                    let Ok(task) = task_receiver.recv() else {
                        return;
                    };
                    drop(task_receiver);
                    task();
                }));
            }
            Self {
                handles,
                task_sender,
            }
        }
    
        pub fn spawn_task<F: FnOnce() + Send + 'static>(&self, f: F) {
            self.task_sender.send(Box::new(f)).expect("All threads ended");
        }
    
        pub fn join(self) -> thread::Result<()> {
            drop(self.task_sender);
            for handle in self.handles {
                handle.join()?;
            }
            Ok(())
        }
    }
    
    #[test]
    fn thread_pool() {
        let pool = ThreadPool::new();
        for i in 0..500 {
            pool.spawn_task(move || {
                println!("Task {} working on thread {:?}", i, thread::current().id());
            });
        }
        pool.join().unwrap();
    }
    Ответ написан
    3 комментария
  • Как правильно сложить дату-время в массив?

    bingo347
    @bingo347
    Crazy on performance...
    use chrono::prelude::*;
    use std::sync::mpsc;
    use std::sync::mpsc::{Receiver, Sender};
    use std::{thread, time};
    
    fn main() {
        println!("- - - - -");
        let mut children = Vec::with_capacity(3);
    
        for id in 0..children.capacity() {
            let child = thread::spawn(move || {
                let mut date_times = Vec::with_capacity(5);
                for i in 0..date_times.capacity() {
                    let t: DateTime<Local> = Local::now();
                    date_times.push(t);
                    println!("{:?}_ поток, задача _{:?}, время: {:?}", id, i, t);
                    thread::sleep(time::Duration::from_millis(3));
                }
                (id, date_times)
            });
    
            children.push(child);
        }
    
        for child in children {
            let (id, date_times) = child.join().expect("Дочерний поток паникует");
            println!("thd_{} = {:?}", id, date_times);
        }
        println!("- - - - -");
    }
    Ответ написан
    1 комментарий
  • Как сделать много вставок в HashMap за минимальное время?

    bingo347
    @bingo347
    Crazy on performance...
    Начну с того, что Вы абсолютно не понимаете зачем нужен async. Есть 2 вида нагрузки - CPU-bound и I/O-bound (где I/O - это input/output). Особенность I/O-bound нагрузки в том, что процессор большую часть времени простаивает в ожидании I/O операций (диска, сети, базы данных и т.д.). Async как раз решает эту задачу за счет кооперативной многозадачности, пока одна задача ждет ответ от I/O мы можем нагрузить CPU другой задачей. Естественно все это не бесплатно, но выигрыш тут в отсутствии простоя CPU за счет того, что мы запускаем тысячи задач на небольшом количестве потоков (в некоторых средах, вроде node.js или asyncio в python такой поток вообще 1), например tokio по-умолчанию запускает пул потоков по количеству ядер.
    Из этого уже можно выделить проблемы в Вашем коде:
    #[tokio::main(flavor = "multi_thread", worker_threads = 1024)]
    1024 - потеряли весь профит от небольшого числа потоков, теперь ОС будет распределять 1024 потока на небольшое количество ядер CPU.
    async fn set(&mut self, name: String, value: String) {
        self.data.insert(name, value);
    }
    у этого метода нет ни одной причины быть асинхронным, операции с HashMap - чистый CPU-bound.

    Вообще, данную задачу можно распараллелить, и для этого достаточно обычных потоков:
    fn main() {
        let start = Instant::now();
        let handles: Vec<_> = (0..4)
            .map(|table_index| {
                std::thread::spawn(move || {
                    let mut table = Table::new();
                    for i in (0..3000000).filter(|i| (i % 4 + 1) == table_index) {
                        table.set(format!("{}", i), format!("value{}", i));
                    }
                    table
                })
            })
            .collect();
        for handle in handles {
            let _table = handle.join().unwrap();
            // тут добавляем таблицы в менеджер
        }
        let elapsed = start.elapsed();
    
        println!("Time taken to set 3,000,000 keys: {:?}", elapsed);
    }
    и даже это можно заморочиться и улучшить, например запускать потоков не больше std::thread::available_parallelism() или оптимизировать счетчик для каждой таблицы (
    (0..3000000).filter(|i| (i % 4 + 1) == table_index)
    ), но это я оставлю Вам в качестве д/з.

    В общем, Ваша проблема не в том, что какие-то структуры/библиотеки медленные, а в том, что Вы их используете не по назначению.
    Ответ написан
    2 комментария
  • Как в rust конвертировать place expression в value expressions?

    bingo347
    @bingo347
    Crazy on performance...
    Проблема с типами тут.
    Тип fn() -> u8 - это указатель на функцию. В него можно записать или обычную функцию с подходящей сигнатурой или замыкание, которое ничего не замыкает:
    fn some_func() -> u8 {
        0
    }
    
    let f: fn() -> u8 = some_func;
    let f: fn() -> u8 = || 0;

    Если же замыкание что-то замыкает (захватывает переменные из окружения), то это уже анонимная структура, хранящая в себе все захваченные значения (или ссылки на них), и у этой структуры просто перегружен оператор круглые скобки через трейты Fn/FnMut/FnOnce. Притом, так как компилятор генерирует для каждого замыкания в коде свою анонимную структуру, у каждого замыкания будет свой уникальный тип.

    Если массив замыканий полностью формируется в цикле, то замыкания будут одного типа и все скомпилируется:
    fn main() {
        let mut funcs = Vec::with_capacity(3);
        for i in 1u8..=3 {
            funcs.push(move || i);
        }
        
        for i in 0..3 {
            println!("{}", funcs[i]());
        }
    }


    P.S. для рэнжей есть литералы, не нужно их создавать через std::ops::RangeInclusive::<u8>::new
    Ответ написан
    2 комментария
  • Зачем нужен метод clone() если есть ссылки?

    bingo347
    @bingo347
    Crazy on performance...
    Метод clone из трейта Clone производит копию некоторого ресурса, мы получаем еще один ресурс во владение. При этом данные не обязательно копируются, логика может быть любой, например Rc и Arc в нем просто увеличивают счетчик ссылок, а ресурс остается одним, разделяя владение на несколько владельцев.
    Ссылки ограничены временем жизни ресурса, на который они ссылаются, к тому же менять ресурс мы можем только по уникальной ссылке, а метод clone создаст копию ресурса и отдаст владение этой копией, что позволит этой копии жить дольше, чем исходный ресурс, а так же изменять эту копию независимо от исходного ресурса.
    Ответ написан
    1 комментарий
  • Как изменить данные завернутые в Box, который завернут в Option?

    bingo347
    @bingo347
    Crazy on performance...
    Во-первых стоит познакомится с такой конструкцией, как if let.
    Но здесь, с Box, у Вас не получится обойти borrow checker.
    Нормальным решением будет использовать счетчики ссылок (Rc или Arc) и избавиться с их помощи от зависимости на лайфтаймы. А для ссылки на parent нужно использовать Weak ссылку, чтоб она не влияла на подсчет ссылок.

    https://doc.rust-lang.org/std/rc/struct.Rc.html
    https://doc.rust-lang.org/std/rc/struct.Weak.html

    https://doc.rust-lang.org/std/sync/struct.Arc.html
    https://doc.rust-lang.org/std/sync/struct.Weak.html

    use std::rc::{Rc, Weak};
    
    pub struct Node {
        parent: Weak<Node>,
        data: u32,
        pub left: Option<Rc<Node>>,
        pub right: Option<Rc<Node>>,
    }
    
    #[derive(Debug)]
    pub struct SetChildError;
    
    impl Node {
        pub fn new(data: u32) -> Self {
            Self {
                parent: Weak::new(),
                data,
                left: None,
                right: None,
            }
        }
    
        pub fn set_left(self: &mut Rc<Self>, mut child: Rc<Self>) -> Result<(), SetChildError> {
            let child_mut = Rc::get_mut(&mut child).ok_or(SetChildError)?;
            child_mut.parent = Rc::downgrade(self);
    
            let self_mut = Rc::get_mut(self).ok_or(SetChildError)?;
            self_mut.left = Some(child);
    
            Ok(())
        }
    }
    Ответ написан
    5 комментариев