Ответы пользователя по тегу Rust
  • Слайс на неинициализированную память?

    bingo347
    @bingo347
    Crazy on performance...
    MaybeUninit под капотом - это union, то есть компилятор ничего не знает, инициализированное там значение или нет и перекладывает эту ответственность на программиста. А значит UB здесь не будет.
    То есть спокойно делаете &mut [MaybeUninit<*mut State>], инициализируете элементы и после std::mem::transmute в &mut [*mut State]
    Ответ написан
    2 комментария
  • Возможно ли собрать данные из итератора в уже созданный буфер?

    bingo347
    @bingo347
    Crazy on performance...
    let arr: [i32; 10] = [1, 2, 3, 4, 5, 6, 7, 8, 9, 10];
    let mut arr2: [i32; 10] = [0; 10];
    
    arr.iter()
        .zip(arr2.iter_mut())
        .filter(|(el, _)| **el % 2 == 0)
        .for_each(|(el, target)| {
            *target = *el;
        });
    Ответ написан
    Комментировать
  • Как исправить ошибку error: linking with `link.exe` failed: exit code: 1120?

    bingo347
    @bingo347
    Crazy on performance...
    Попробуйте добавить build скрипт (файл build.rs на том же уровне где Cargo.toml) со следующим содержимым:
    fn main() {
        println!("cargo:rustc-link-lib=dylib=Gdi32.dll");
    }
    Ответ написан
  • Почему не работает прокси?

    bingo347
    @bingo347
    Crazy on performance...
    Вот по Вашей же ссылке на доку:

    https://docs.rs/reqwest/latest/reqwest/struct.Prox...
    Proxy all HTTP traffic to the passed URL.
    Проксирует весь HTTP трафик

    https://docs.rs/reqwest/latest/reqwest/struct.Prox...
    Proxy all HTTPS traffic to the passed URL.
    Проксирует весь HTTPS трафик

    https://docs.rs/reqwest/latest/reqwest/struct.Prox...
    Proxy all traffic to the passed URL.
    Пооксирует вообще весь трафик
    Ответ написан
    1 комментарий
  • FnMut в немутабельной переменной?

    bingo347
    @bingo347
    Crazy on performance...
    Мутабельность нужна для вызова, так как FnMut вызывается по мутабельной ссылке.
    Здесь же только передаётся владение в метод run

    P.S. что-то мне подсказывает, что сигнатура не совсем корректная. Отсутствует объявление дженерика T (и возможно его ограничений), отсутствует лайфтайм для ссылки в колбэке:
    pub fn run<T, F>(self, event_handler: F) -> Result<(), EventLoopError>
        where
            F: for<'a> FnMut(Event<T>, &'a EventLoopWindowTarget<T>),
    {
        self.event_loop.run(event_handler)
    }
    Ответ написан
    Комментировать
  • Каких вещей следует избегать в Rust?

    bingo347
    @bingo347
    Crazy on performance...
    Я знаю, что следует избегать всяких "продвинутых" штук из ряда связных списков, самореферентных структур и т.п.
    Односвязные списки никаких проблем не доставляют (ну кроме того, что они плохо ложатся на процессорный кэш). Для двусвязных списков и самореферентных структур придётся использовать сырые указатели и unsafe.

    Ещё я обнаружил, что создание больших структур, с методами, с кучей полей, обычно приводит к проблемам с borrow checker.
    Borrow checker абсолютно плевать на размер структур. Это никак не связано.

    А если в структуре будет ссылка или иное заимствование, то это гарантированные проблемы.
    Нет ни каких проблем.

    Насколько я понимаю, самым рабочим выглядит чисто функциональный подход, а не структур с методами.
    Одно другому никак не противоречит.

    И правильно ли я понимаю, что следует избегать структур хранящих ссылки и имеющими лайфтайм?
    Не правильно.

    Так, наличие в умеренных размерах программе, которая по сути была одной функцией, лишь одной структуры хранящей ссылку, поставило крест на попытке структуризации программы в более человеческий вид.
    Что-то делаете не так. Без конкретных примеров кода сказать сложно.

    И очень часто в Rust программах, мне приходится идти на более уродливую архитектуру, дабы избежать проблем с (почти ненужным в однопоточном коде) borrow checker.
    Что-то делаете не так. Скорее всего просто не понимаете borrow checker и пытаетесь писать на новом языке так, как привыкли в каком-то другом.

    И в вопросе о borrow checker, разве не является тот факт, что большинство библиотек избегает &mut self в изменяющих что-то методах, звоночком к наличию большим проблем в языке?
    О каком большинстве речь? Библиотеки используют мутабельные ссылки там где это нужно. Если метод действительно что-то меняет, то будет мутабельная ссылка ну и иногда будет использоваться interior mutability там где это необходимо. В языке нет проблем с мутабельными ссылками.

    В общем, посоветуйте что-то что-бы помогало меньше бороться с borrow checker, потому что сейчас я очень много времени трачу именно на это.
    Для начала понять его. Понять какую проблему он решает. Почитайте, что такое undefined behavior. Почитайте, что такое алиасинг.

    Возможно где-то альтернативой мутабельным ссылкам будут Cell/RefCell в однопоточном коде и Mutex/RwLock в многопоточном.
    Возможно если покажете примеры кода, где у Вас проблемы, то можно будет подсказать что-то более конкретное.
    Ответ написан
    3 комментария
  • Возможно ли выполнить JavaScript код в Rust при помощи NodeJS?

    bingo347
    @bingo347 Куратор тега JavaScript
    Crazy on performance...
    Хоть и вопрос несколько сумбурный и не совсем понятно, что автор хочет на самом деле...
    Но краткий ответ: да, возможно, но есть нюансы...

    Самый простой вариант - запускать node как отдельный процесс. Можно просто запускать на каждый чих, а можно заморочиться и организовать общение между приложением на Rust и приложением на Node.js

    Вариант поинтереснее - Node.js можно собрать как библиотеку (есть даже готовые бинарные сборки такого), линкуем с приложением на Rust. Всё живёт в одном процессе, но node будет запускать свои потоки.

    Есть и обратный последнему вариант. Можно из Rust кода собрать бинарный аддон к Node.js
    Правда тут уже JS код будет запускать код на Rust.
    https://napi.rs
    https://neon-rs.dev

    Можно пойти дальше. Если задача просто исполнять JS код из приложения на Rust, то можно слинковаться с одним из JS движков. Приколюх предоставляемых платформой Node.js здесь не будет (вроде fs или http), хотя никто не мешает реализовать это самостоятельно.
    https://crates.io/crates/v8
    https://crates.io/crates/quickjs_runtime
    Ответ написан
    3 комментария
  • Как реализовать превращение вектора объектов в дерево?

    bingo347
    @bingo347
    Crazy on performance...
    Главная проблема тут - лайфтаймы. В частности у переменной на стеке никогда не будет лайфтайма 'static.
    Ну и отдавать в куда либо долгоживущее ссылку на временное значение - UB. Благо лайфтаймы и тут спасают.
    Собственно для этого Rc и нужен - он владеющая ссылка. Соответственно в нём должно быть значение, а не ссылка (ссылка на ссылку редко когда нужна).

    use std::cell::RefCell;
    use std::rc::Rc;
    
    #[derive(Default, Clone, Debug)]
    struct NodeInner {
        name: String,
        source: String,
    
        path: String,
        leaf: bool,
        children: Vec<Node>,
    }
    
    #[derive(Default, Clone, Debug)]
    struct Node(Rc<RefCell<NodeInner>>);
    
    impl Node {
        fn fast(name: impl Into<String>, source: impl Into<String>) -> Self {
            Self(Rc::new(RefCell::new(NodeInner {
                name: name.into(),
                source: source.into(),
    
                path: String::new(),
                leaf: true,
                children: Vec::new(),
            })))
        }
    
        fn insert(&self, node: Node) {
            let mut current = self.clone();
            for part in node.0.borrow().source.split(".") {
                let inner = node.0.borrow();
                let found = inner.children.iter().find(|&v| v.0.borrow().path == part);
    
                match found {
                    None => {
                        let tmp = Node::fast(String::new(), String::new());
                        current.0.borrow_mut().children.push(tmp.clone());
                        current = tmp;
                    }
                    Some(val) => current = val.clone(),
                }
            }
        }
    }
    
    fn main() {
        let mut root = Node::default();
        root.insert(Node::fast("First", "e"));
        root.insert(Node::fast("First", "e.sources"));
        root.insert(Node::fast("First", "e.sources.two"));
        root.insert(Node::fast("First", "e.secret"));
        root.insert(Node::fast("First", "e.secret.left"));
        root.insert(Node::fast("First", "e.secret.right"));
        print!("{:#?}", root);
    }

    Как заполнять поля уже сами разбирайтесь, думаю тут не сложно
    Ответ написан
    3 комментария
  • Вызов функций С++ из .h?

    bingo347
    @bingo347
    Crazy on performance...
    Скорее всего придётся сделать extern C обёртку над библиотекой и биндится уже с ней. Ну и надо понимать, что никакие абстракции плюсов вроде темплейтов, классов и т.д. в раст не протащить.

    В какой-то степени может помочь cxx, но надо понимать, что эта штука тоже не всесильна.
    Ответ написан
  • Почему время жизни параметров берутся из дженериков?

    bingo347
    @bingo347
    Crazy on performance...
    Советую почитать вот эту статью: https://habr.com/ru/articles/515034/

    А вообще, времена жизни - это часть типа.
    Условный u32 имеет время жизни 'static
    А ссылка на u32 - &'a u32 (где 'a это диапазон от объявления исходного u32 до его последнего использования) будет иметь время жизни 'a
    Пример по сложнее, ссылка на ссылку - &'a &'b u32 - будет иметь время жизни наименьшее из 'a и 'b

    Передаётся через дженерик, так как функция должна работать с абстрактным временем жизни
    fn example<'a>(r: &'a u32) -> &'a u32 { r }
    
    fn f_a() {
        let a = 1;
        let r = example(&a);
    }
    
    fn f_b() {
        let b = 1;
        let r = example(&b);
    }
    В этом примере очевидно что переменная a в f_a будет иметь время жизни отличное от b в f_b, но example спокойно работает и с тем и с другим, то есть она является обобщённой по времени жизни, в первом случае она вернёт ссылку с временем жизни как у переменной a, во втором - как у b.

    А ещё помимо времен жизни и типов в дженериках могут быть некоторые константы:
    fn make_array<const SIZE: usize>(el: u32) -> [u32; SIZE] {
        [el; SIZE]
    }
    
    let arr = make_array::<3>(1); // [1, 1, 1]
    Ответ написан
    3 комментария
  • Что делает метод as_bytes?

    bingo347
    @bingo347
    Crazy on performance...
    Представление структуры в памяти в виде байтов
    Ответ написан
  • Вызов drop в вызове drop?

    bingo347
    @bingo347
    Crazy on performance...
    Почему drop принимает ссылку, а не значение?

    Потому что метод drop трейта Drop вызывается компилятором, каждый раз когда переменная владеющая чем-либо выходит из области видимости. И сам метод drop тут не исключение. То есть если бы self тут был по значению, компилятор был бы обязан его дропнуть в конце функции, что вызвало бы бесконечную рекурсию.

    Причём если вызывать drop() руками
    Функция core::mem::drop никакого отношения к трейту Drop не имеет. Если Вы глянете на её реализацию, то это просто пустая функция, которая принимает аргумент по значению, а он уже дропается на общих основаниях, так как выходит из области видимости в ней.

    Почему сначала вызывается drop для A, а потом для B? По логике drop должен сначала вызываться для полей.
    У Вас неверная логика. В метод трейта Drop приходит ссылка, а значит должна быть гарантия того что данные по ней полностью валидные. Всегда дропается сначала внешняя структура,а затем её поля. Более того компилятор не даст Вам даже мувнуть части структуры имплиментирующей Drop.

    Если очень нужно, то владение из поля можно забрать через std::mem::swap/std::mem::replace/std::mem::take
    Хотя проще это сделать обернув такое поле в Option и забирая владение его методом take
    Ответ написан
    2 комментария
  • Как написать такой макрос?

    bingo347
    @bingo347
    Crazy on performance...
    Я бы тут не парился, и превращал бы это:
    #[r]
    fn bar(a: usize) -> usize {
        if a % 2 == 0 {
            return 0;
        }
        1
    }


    В это:
    fn bar(a: usize, res: *mut usize) {
        fn bar_impl(a: usize) -> usize {
            if a % 2 == 0 {
                return 0;
            }
            1
        }
        unsafe { *res = bar_impl(a); }
    }
    Главная фишка в том, что исходный код оставляем без изменений, парсить надо только сигнатуру (что с использованием syn - легко).
    По сути просто генерируем обёртку. Для универсальности стоит учесть async fn.

    Ну и если совсем по хорошему, то тут легко UB поймать с сырым указателем, и генерируемую функцию имеет смысл делать unsafe.
    Ответ написан
    Комментировать
  • Возврат собственного типа ошибки из main с печатью сообщения об ошибке?

    bingo347
    @bingo347
    Crazy on performance...
    fn main() -> MainResultWrapper {
        MainResultWrapper(main_inner())
    }
    
    #[inline(always)]
    fn main_inner() -> Result<(), GlobalError> {
        let config = read_config_from_file()?;
        Ok(())
    }
    Ответ написан
    Комментировать
  • Как внести в массив символы, которым соответствует диапазон u8?

    bingo347
    @bingo347
    Crazy on performance...
    fn main() {
        let src: Vec<[u8; 1]> = (0..u8::MAX).map(|i| [i]).collect();
        let mut info =  Vec::<&str>::with_capacity(u8::MAX.into());
        for u in &src {
            let t = std::str::from_utf8(&*u).unwrap_or("Err");
            info.push(t);
        }
        println!("\n{:#?}", info);
    }

    но надо понимать, что info будет связан лайфтаймом с src, чтоб избавится от этого нужно хранить в нём не &str а String или Box<str>
    fn main() {
        let info: Vec<Box<str>> = (0..u8::MAX).map(|i| {
            let u = [i];
            let t = std::str::from_utf8(&u).unwrap_or("Err");
            t.into()
        }).collect();
        println!("\n{:#?}", info);
    }
    Ответ написан
    1 комментарий
  • Как использовать модуль Win32 крейта windows в раст?

    bingo347
    @bingo347
    Crazy on performance...
    Попробуйте фичу Win32_System_Threading
    Ну и вот тут можно поискать если ещё что-то потребуется: https://microsoft.github.io/windows-rs/features/#/...
    Ответ написан
    Комментировать
  • Как исправиться ошибку lifetime?

    bingo347
    @bingo347
    Crazy on performance...
    impl<'a> List<'a> {
        fn add(&mut self, val: String, referenced_node: &'a Node) {
            self.node = Some(Node {
                data: String::from(String::from(val)),
                next: Some(referenced_node),
            });
        }
    }
    правда скорее всего будут траблы уже в месте вызова этого метода, но что бы поправить недостаточно контекста

    Вообще думаю тут имеет смысл использовать Box/Rc/Arc вместо ссылок
    Ответ написан
    Комментировать
  • Как работает этот код?

    bingo347
    @bingo347
    Crazy on performance...
    Очень упрощенно HashMap можно представить следующим образом:
    pub struct HashMap<K, V> {
        table: Table<(K, V)>,
    }
    
    struct Table<T> {
        // битовая маска занятых ячеек в items
        mask: u64,
        items: Box<[std::mem::MaybeUninit<Item<T>>; 64]>,
        len: usize,
    }
    
    struct Item<T> {
        data: T,
        next: Option<std::ptr::NonNull<Item<T>>>,
    }


    А Entry так:
    pub enum Entry<'a, K, V> {
        Vacant(VacantEntry<'a, K, V>),
        Occupied(OccupiedEntry<'a, K, V>),
    }
    
    pub struct VacantEntry<'a, K, V> {
        hash: u64,
        key: K,
        table: &'a mut Table<(K, V)>,
    }
    
    pub struct OccupiedEntry<'a, K, V> {
        elem: Bucket<(K, V)>,
        table: &'a mut Table<(K, V)>,
    }
    
    // указатель на Item.data
    struct Bucket<T> {
        ptr: std::ptr::NonNull<T>,
    }


    Как можно заметить у Entry есть лайфтайм, который связывает его с HashMap от которой он создан. А внутри есть мутабельная ссылка с этим лайфтаймом на таблицу с данными HashMap.
    Метод entry упрощенно выглядит примерно так:
    impl<K, V> HashMap<K, V> {
        pub fn entry<'a>(&'a mut self, key: K) -> Entry<'a, K, V>
        where
            K: Eq + std::hash::Hash,
        {
            use std::hash::Hasher as _;
            let mut hasher = self.get_hasher();
            key.hash(&mut hasher);
            let hash = hasher.finish();
    
            if let Some(elem) = self.table.find(hash, |(k, _)| key == *k) {
                Entry::Occupied(OccupiedEntry {
                    elem,
                    table: &mut self.table,
                })
            } else {
                Entry::Vacant(VacantEntry {
                    hash,
                    key,
                    table: &mut self.table,
                })
            }
        }
    
        fn get_hasher(&self) -> impl std::hash::Hasher {
            todo!()
        }
    }
    
    impl<T> Table<T> {
        fn find(&self, hash: u64, is_match: impl FnMut(&T) -> bool) -> Option<Bucket<T>> {
            todo!()
        }
    }

    Как видим мутабельная ссылка всё же есть, только она завернута в структуру, так как одной этой ссылки не достаточно, так как в случае свободной Entry нам нужно хранить ещё и ключ, а заодно и хэш (чтоб не считать его снова), а в случае занятой - указатель на бакет (область памяти где храниться пара ключ и значение).
    Ответ написан
    Комментировать
  • Как работают пакеты и крейты?

    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...
    У Вас относительный путь, относительные пути зависят от контекста запуска, то есть считаются они относительно той папки из которой производится запуск.
    То есть данная программа будет искать разные файлы в зависимости от того из какой папки Вы её запустите.
    Ответ написан