Ответы пользователя по тегу Rust
  • Как открывать много 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 комментариев
  • Правильно ли решил задачу?

    bingo347
    @bingo347
    Crazy on performance...
    Задачка прикольная для обучения, позволяет поработать сразу со многими конструкциями языка.

    Для начала комментарии по Вашему решению:
    1. Тип String алоцирует память на куче, операция += над типом String может приводить к реалокации памяти. Алокация памяти не дешевая операция, в данной задаче можно обойтись вообще без нее.
    2. Для повторяющихся операций придумали циклы, а с циклами хорошо сочетаются коллекции, например массивы и слайсы.
    3. Уже писали в комментах, но все же, if true - бесполезная штука.

    Теперь давайте посмотрим на текст песни. У нас 12 куплетов.
    Каждый куплет начинается с очень похожих строчек: "On the first day of Christmas", "On the second day of Christmas", и т.д., меняется только числительное.
    Потом всегда идет строчка "My true love sent to me" в каждом куплете.
    Затем идет от 1 до 12 строчек, повторяющиеся из куплета в куплет, но в i-том куплете будет только i таких строчек. Притом тут есть особенность, что первая строчка в куплете иногда отличается от своих аналогов в последующих куплетах.
    Ну и наконец, в 12 куплете мы видим, что строка "And a partridge in a pear tree" присутствует 2 раза, что выбивается из общего правила.

    Решение с объяснением

    Для начала вынесем факт того, что у нас 12 куплетов в константу, это нам пригодится для дальнейшего объявления массивов и итерации по ним.
    const NUMBER_OF_VERSES: usize = 12;

    Заведем массив числительных, которые меняются в первых строчках куплетов.
    const NUMERALS: [&str; NUMBER_OF_VERSES] = [
        "first", "second", "third", "fourth", "fifth", "sixth", "seventh", "eighth", "ninth", "tenth",
        "11th", "12th",
    ];


    Так же заведем массив, для повторяющихся строчек куплетов.
    const LINES: [&str; NUMBER_OF_VERSES] = [
        "And a partridge in a pear tree",
        "Two turtle-doves",
        "Three French hens",
        "Four calling birds",
        "Five golden rings (five golden rings)",
        "Six geese a-laying",
        "Seven swans a-swimming",
        "Eight maids a-milking",
        "Nine ladies dancing",
        "Ten lords a-leaping",
        "Eleven pipers piping",
        "12 drummers drumming",
    ];


    Так же нам понадобится массив первых строчек куплетов, так как среди них есть отличающиеся. Я нашел такие в 1 и 11 куплете (так как массивы у нас индексируются с 0, то они будут под индексами 0 и 10 соответственно). Остальные же будут такими же как в массиве LINES, а значит можно по экономить размер бинаря и занимаемую память за счет того что в массивах у нас только ссылки на строки, которые можно копировать.
    Тут конечно можно написать что-то вроде
    const DIFFERING_LINES: [&str; NUMBER_OF_VERSES] = [
        "A partridge in a pear tree",
        LINES[1],
        LINES[2],
        // ...
    ];
    но это дикая копипаста, которая плохо читается и подвержена ошибкам.
    Благо в Rust есть константные функции, которые могут выполняться в compile-time и возвращают константы, а значит можно наш константный массив сгенерировать. Правда константные функции довольно сильно ограничены, в них можно пользоваться лишь ветвлениями, циклами, простейшей арифметикой (в том числе над указателями, а значит и получать доступ к элементам массива по индексу). Так же можно вызывать другие константные функции. Цикл for нам к сожалению тут не доступен, так как он работает поверх итераторов, а методы IntoIter::into_iter и Iterator::next, которые он вызывает, не являются константными. Но цикл со счетчиком можно сделать и через while. По итогу получим такую функцию:
    const fn gen_differing_lines() -> [&'static str; NUMBER_OF_VERSES] {
        let mut i = 0;
        let mut lines = [""; NUMBER_OF_VERSES];
        while i < NUMBER_OF_VERSES {
            lines[i] = match i {
                0 => "A partridge in a pear tree",
                10 => "I sent 11 pipers piping",
                i => LINES[i],
            };
            i += 1;
        }
        lines
    }

    И инициализируем ей наш массив:
    const DIFFERING_LINES: [&str; NUMBER_OF_VERSES] = gen_differing_lines();


    Теперь еще особенность, в самих куплетах, строчки из LINES в них идут в обратном порядке. То есть для 4 куплета (индекс 3) нам помимо строчки DIFFERING_LINES[3] нам нужно напечатать строки под индексами 2, 1 и 0 из LINES. Для удобства вынесем печать строк из LINES в отдельную функцию:
    fn print_verse(mut i: usize) {
        while i > 0 {
            i -= 1;
            println!("{}", LINES[i]);
        }
    }


    Ну и осталось написать основной код для печати:
    fn main() {
        for i in 0..NUMBER_OF_VERSES {
            println!("On the {} day of Christmas", NUMERALS[i]);
            println!("My true love sent to me");
            println!("{}", DIFFERING_LINES[i]);
            print_verse(i);
        }
        println!("{}", LINES[0]);
    }


    А полное решение можно посмотреть и запустить тут:
    https://play.rust-lang.org/?version=stable&mode=re...
    Ответ написан
    Комментировать
  • Почему wasm код не генерируется при компиляции rust кода с сырыми указателями?

    bingo347
    @bingo347
    Crazy on performance...
    Начну пожалуй с того, что в первом примере у Вас UB (undefined behavior):
    #[no_mangle]
    pub fn test() {
        // Вот тут на стеке выделено 4 байта
        let mut idx: i32 = 10;
        // Указатель на эти 4 байта
        let ptr = &mut idx as *mut i32;
    
        // По сути указатель на те же 4 байта, но на деле указатель на 40 байт
        let buffer = unsafe { core::slice::from_raw_parts_mut(ptr, 10) };
    
        // И тут мы работаем с этими 40 байтами,
        // но нормальные из них только 4 байта в начале,
        // а остальные смотрят в глубь стека и меняют его
        // то есть перетирают адрес возврата и данные на стеке вызвавшей функции
        for pixel in buffer.iter_mut() {
            *pixel = 0x7d2b7500;
        }
    }
    То есть после завершения данная функция должна не вернуться в вызывающую функцию, а передать управление непонятно куда.

    Ну и еще один момент, это оптимизация, данная функция не имеет сайд эффектов и ничего не возвращает. После инлайна функции core::slice::from_raw_parts_mut и метода Iterator::iter_mut компилятор вполне может понять, что данный код не имеет сайд эффектов, а значит бесполезен и его можно вырезать. Вот собственно он его и вырезает.
    Во втором же примере у нас есть сайд эффект - изменение данных по указателю пришедшему извне через аргументы.

    P.S. Во втором примере кстати тоже можно увидеть работу оптимизации, цикл из 10 итераций, пишущих i32 в смещение по указателю, был заменен на обычные 5 записей i64 в смещение по указателю.
    Ответ написан
    1 комментарий