Страницы

Поиск по вопросам

Показаны сообщения с ярлыком rust. Показать все сообщения
Показаны сообщения с ярлыком rust. Показать все сообщения

четверг, 9 апреля 2020 г.

Прочитать с середины файл без чтения его начала на Rust'е

#файлы #потоки_данных #filestream #rust #fstream

                    
let f = File::open("file").unwrap();
let reader = BufReader::new(f).seek(io::SeekFrom::Start(2));
for b in reader.iter().next() {
    println!("{}", b);
}


Выводит то, что находится внутри io::SeekFrom::Start, т.е. 2.
    


Ответы

Ответ 1



Какой-то странный цикл у вас. По идее должно быть как-то так: let f = File::open("file").unwrap(); let mut reader = BufReader::new(f); reader.seek(io::SeekFrom::Start(2)).unwrap(); for b in reader.bytes() { println!("{}", b); } Метод seek() возвращает Result, то есть новую позицию в файле. Он вызывается на &mut self, значит reader должен быть mut, иначе не заработает. Что делает ваш код: вы открываете файл и смещаете указатель на 2 байта, сохраняя Result в reader. Потом вы преобразовываете Result в итератор с помощью iter() и вызываете на итераторе next(), получаете Option, равный Some(2). А потом по опции итерируете (Option<_> реализует IntoIterator, так что этому типу можно итерировать). Итератор весело выдаёт один элемент — 2. Чтобы избежать подобных ошибок в будущем, внимательно читайте документацию на используемые трейты и типы: ни в трейте Read, ни в типе BufReader нет метода iter(), однако в Read есть метод bytes(), который вам был нужен. А вот метод Seek::seek возвращает Result. Кроме того стоит больше прочитать про интерфейс итераторов, так как вы, судя по всему, до конца не поняли эту идею, раз вызываете на итераторе метод next() явно.

вторник, 31 марта 2020 г.

Rust: преобразование обобщённого типа в конкретный

#generics #rust #преобразование_типов


Собственно код:

extern crate num_traits;

pub trait DigitCount {
    fn decimal_digit_count(&self) -> usize;
}

impl > DigitCount for T {
    fn decimal_digit_count (&self) -> usize {
        if self.is_zero() {
            1
        } else {
            self.into().abs().log10().trunc() as usize + 1
        }
    }
}


Метод decimal_digit_count должен вызываться для любого целого значения и возвращать
количество десятичных разрядов в нём:

println!("{}", 123u64.decimal_digit_count());


Вопрос: как правильно записать преобразование T → f64 для последующего вызова методов
abs, log10, trunc? В текущем виде компилятор требует аннотацию типа для into(), но
если её подставить (into::()), то говорит, что ожидается 0 параметров типов.
    


Ответы

Ответ 1



У самого метода и правда нет типового параметра, потому что он есть у типажа: pub trait Into { fn into(self) -> T; } Первый вариант исправления - просто явно указать через промежуточную переменную какой тип мы в итоге хотим получить: let f: f64 = self.into(); f.abs().log10().trunc() as usize + 1 (playpen) Второй вариант - использовать UFCS форму вызова метода с явным обозначением T у типажа: Into::::into(self).abs().log10().trunc() as usize + 1 (playpen) Метод decimal_digit_count должен вызываться для любого целого значения и возвращать количество десятичных разрядов в нём На всякий случай еще добавлю что преобразование в f64 реализовано не для всех целых типов. Такое преобразование обязано всегда заканчиваться успехом, что невозможно, например, для u64 или i128 из-за невозможности вместить весь диапазон значений.

воскресенье, 8 марта 2020 г.

Rust. Вызов метода у Arc<T>

#rust #mutex


Читаю Rustbook и не понимаю некоторые вещи.     

use std::sync::{Arc, Mutex};
use std::thread;

fn main() {
    let data = Arc::new(Mutex::new(vec![1u32, 2, 3]));
    for i in 0..3 {
        let data = data.clone(); //(1)
        thread::spawn(move || {
            let mut data = data.lock().unwrap(); //(2)
            data[i] += 1;
        });
    }
    thread::sleep_ms(50);
}


(1). Метод clone() возвращает тип self, т.е. Arc<_>. В этом можно убедиться, если
явно написать тип:

let data: Arc>> = data.clone();


В связи с этим возникает вопрос: почему в (2) мы вызываем метод lock() так, если
бы мы работали напрямую с Mutex? Все как в C++, но там у shared_ptr оператор -> перегружен
за нас, а покопавшись в исходниках arc.rs я не увидел подобного или пропустил.

UPD

Спасибо Кнопкатык за наводку на Deref coercions. Для закрепления написал такой искусственный
пример:

use std::ops::Deref;

#[derive(Debug)]
struct Field {
    x: i32,
}

impl Field{
    fn print(&self){
        println!("x = {}", self.x);
    }
}

#[derive(Debug)]
struct Example {
    x: Field,
}

impl Deref for Example{
    type Target = Field;

    fn deref(&self) -> &Field{
        &self.x
    }
}

fn main() {
    let s = Example { x: Field { x: 5} };
    s.print();
}


Структура Example не имеет имплементации метода print.
Но если вспомнить универсальный синтаксис вызова функций, то 

s.print();


можно заменить на:

Field::print(&s);


Следовательно, так как у нас есть реализация типажа Deref для Example, возвращающая
&Field, то значения Example, если нужно, будут автоматически преобразовываться в &Field
при разыменовании:

Field::print(&Example) -> dereference -> Field::print(&Field);


Код, как и предполагается, выведет:

x = 5

    


Ответы

Ответ 1



Дело в механизме Deref coercions или "Преобразования при разыменовании". Arc реализует типаж Deref, значения &Arc будут автоматически преобразованы в &T, когда это необходимо. Метод lock(&self) Мютекса принимает &self, и когда мы пытаемся вызвать его с Arc: lock(&Arc) срабатывает механизм Deref coercions lock(&Mutex) а &Mutex это и есть &self для метода lock.

вторник, 25 февраля 2020 г.

Передача двумерного массива в функцию (Rust)

#массивы #функции #rust


Кто-нибудь может подсказать, как передать в функцию двумерный массив?

Желательно по ссылке, т.е. без копирования самого массива. К сожалению их "книга"
пока не богата на примеры передачи параметров в функцию. Вот как я себе это пытался
представить:

fn foo(a:&[&[f64]],x:&[f64])
{
    for i in 0..3 {
        for j in 0..4 {
            println!("{}",a[i][j]);
        }
    }
}

fn main()
{
    let A:[[f64;4];3]=[[1.1,-0.2, 0.1,1.6],
                       [0.1,-1.2,-0.2,2.3],
                       [0.2,-0.1, 1.1,1.5]];
    let mut X:[f64;3]=[0.0;3];

    foo(&A,&X);
}


"X" передается без проблем, а вот на "А" ругается при всех комбинациях [ ] и &, которые
я смог придумать:

src/main.rs:16:6: 16:8 error: mismatched types:
 expected `&[&[f64]]`,
    found `&[[f64; 4]; 3]`
(expected slice,
    found array of 3 elements) [E0308]
src/main.rs:16  foo(&A,&X);
                    ^~
error: aborting due to previous error
Could not compile `example`.

    


Ответы

Ответ 1



Дело в том, что, к примеру, выражение [1i32, 2, 3] возвращает тип [i32; 3] — массив с заданной при компляции длиной. Когда же &[f64] является срезом — ссылочным типом, состоящим из указателя на массив и длины (известной во время выполнения). Вот так будет работать: fn foo(a:&[&[f64]],x:&[f64]) { for i in 0..3 { for j in 0..4 { println!("{}",a[i][j]); } } } fn main() { let a:[&[f64];3]=[&[1.1,-0.2, 0.1,1.6], &[0.1,-1.2,-0.2,2.3], &[0.2,-0.1, 1.1,1.5]]; let x:[f64;3]=[0.0;3]; foo(&a,&x); } К сожалению их "книга" пока не богата на примеры передачи параметров в функцию. Кроме собственно официальной документации по Rust существует очень хорошее руководство с множеством прекрасных примеров Rust by Example.

Ответ 2



Можно делать вот так, получается красивее всего: fn foo, Row: AsRef<[f64]>>(a:Matrix,x:&[f64]) { for i in 0..3 { for j in 0..4 { println!("{}",a.as_ref()[i].as_ref()[j]); } } } fn main() { let A:[[f64;4];3]=[[1.1,-0.2, 0.1,1.6], [0.1,-1.2,-0.2,2.3], [0.2,-0.1, 1.1,1.5]]; let mut X:[f64;3]=[0.0;3]; foo(&A,&X); } Но в этом случае длина массивов не должна превышать 32 (потолок для размерности массивов, для которых в компиляторе поддерживается реализация типажей, в числе которых AsRef/AsMut). Ждем реализации параметризации размерностей.

суббота, 8 февраля 2020 г.

Rust. Проблема с многопоточностью

#многопоточность #rust


Пытаюсь освоить многопоточность в Rust. Прошу ответить на несколько вопросов. Читаю
перевод Rustbook. 


Мы оборачиваем данные в sync::Mutex, когда хотим использовать эти данные (эту переменную)
в других потоках, чтобы не было "гонки данных"? То есть оборачиваем в Mutex, а там
уже когда используем, то lock() и все остальные "становятся в очередь"? 
Я сейчас о примере с Rustbook: 

use std::sync::{Arc, Mutex};
use std::thread;
fn main() {
        let data = Arc::new(Mutex::new(vec![1u32, 2, 3]));
        for i in 0..3 {
                let data = data.clone();
                thread::spawn(move || {
                    let mut data = data.lock().unwrap();
                    data[i] += 1;
            });
    }
    thread::sleep_ms(50);
}



Какие-то странные и непонятные действия. Ну, хорошо, связали data с вектором, который
обернут в Mutex, но зачем ещё в Arc? Внутри цикла зачем-то делаем копию и связываем
с data, потом уже в замыкании опять новая переменная data. 
Объясните пожалуйста. 


Моя попытка сделать десять потоков и в каждом из них прибавлять по единице к data.
Неработающая попытка:

use std::thread;
use std::sync::{Arc, Mutex};

fn main() {
    let mut data = Arc::new(Mutex::new(0));

    let handles: Vec<_> = (0..10).map(|_| {
        thread::spawn(move|| { 
            data.lock();
            data += 1; //binary assignment operation `+=` cannot be applied to type
`alloc::arc::Arc>
        })
    }).collect();

    for h in handles {
        h.join();
    }

}



Почему опять ему (компилятору) не нравится? 
    


Ответы

Ответ 1



В книге очень подробно разобран этот пример с вариантами что будет без мьютексов и что будет без Arc: Arc - является атомарным указателем со счетчиком ссылок. «Атомарный» означает, что им безопасно обмениваться между потоками. Чтобы гарантировать, что его можно безопасно использовать из нескольких потоков, Arc предполагает наличие еще одного свойства у вложенного типа. Он предполагает, что T реализует типаж Sync. В нашем случае мы также хотим, чтобы была возможность изменять вложенное значение. Нам нужен тип, который может обеспечить изменение своего содержимого лишь одним пользователем одновременно. Для этого мы можем использовать тип Mutex Единственное что там вводит в заблуждение - используется затенение переменных, так что там три разных переменных с одинаковым именем data. Вот рабочий код вашего примера: use std::thread; use std::sync::{Arc, Mutex}; fn main() { let data = Arc::new(Mutex::new(0)); let handles: Vec<_> = (0..10).map(|_| { let xd = data.clone();// (1) thread::spawn(move|| { let mut x = xd.lock().unwrap();// (2) *x+=1;// (3) }) }).collect(); for h in handles { h.join(); } println!("{:?}",data) } По пунктам что было не так: Вы пытаетесь непосредственно обратиться к переменной data. Так как для типа Arc не реализован типаж неявного копирования Copy, то первый же поток захватит право владения переменной и она будет недоступна для остальных потоков (и для основного тоже). Поэтому используется явное клонирование указателя data.clone(). Функция lock() возвращает специальный объект MutexGuard дополнительно завернутый в LockResult. Когда этот объект будет освобожден защелка будет отпущена. Поэтому результат возвращаемый lock() нужно сохранить в переменную. unwrap() вытаскивает MutexGuard из LockResult. К тому же этот объект нужен для доступа к самим данным. Для доступа к данным внутри MutexGuard нужно разыменовать то что мы получили в п.2.

пятница, 31 января 2020 г.

Очень большой размер исполняемого модуля

#linux #rust


Я - полный новичёк. Естественно, собрал и запустил hello_world. Потом посмотрел размер
исполняемого модуля:

$ ls -l
итого 664
drwxrwxr-x 2 lary lary   4096 мар  1  2017 build
drwxrwxr-x 2 lary lary   4096 мар  1  2017 deps
drwxrwxr-x 2 lary lary   4096 мар  1  2017 examples
-rwxrwxr-x 1 lary lary 657056 мар  1  2017 hello_world
drwxrwxr-x 2 lary lary   4096 мар  1  2017 native


Цифра 657056 меня как-то огорчила. Я решил, что раз это дебажная сборка, то просто
понапихали туда всякой отладочной информации, вот он и здоровенный такой. Решил собрать
релиз. Посмотрел:

$ ls -l
итого 5152
drwxrwxr-x 2 lary lary    4096 май 22 17:57 build
drwxrwxr-x 2 lary lary    4096 май 22 17:57 deps
drwxrwxr-x 2 lary lary    4096 май 22 17:57 examples
-rwxrwxr-x 2 lary lary 5236496 май 22 17:57 hello_world
-rw-rw-r-- 1 lary lary     102 май 22 17:57 hello_world.d
drwxrwxr-x 2 lary lary    4096 май 22 17:57 incremental
drwxrwxr-x 2 lary lary    4096 май 22 17:57 native 


И стало мне совсем грустно. Я что-то делаю не так? Или это - нормально? По всей видимости,
собран модуль, в который статически прилинкован весь run-time. Так и должно быть ?
    


Ответы

Ответ 1



En SO: Why are Rust executables so huge? Если кратко, то rust по умолчанию влинковывает статически всю свою libstd. Дабы воздержать его от этого можно скормить ему -C prefer-dynamic в аргументы: $ cat hw.rs pub fn main() { println!("Hello world!"); } $ rustc hw.rs -o hw-rust.static $ rustc hw.rs -C prefer-dynamic -o hw-rust.dynamic $ ls -lh hw* -rw-r--r-- 1 alexander users 48 май 24 08:06 hw.rs -rwxr-xr-x 1 alexander users 14K май 24 08:17 hw-rust.dynamic -rwxr-xr-x 1 alexander users 3,0M май 24 08:16 hw-rust.static Update* Стоит иметь в виду, что стабильного ABI у Rust пока нет, и как soname, так и символы динамической линковки меняются от версии к версии. Единственный практически поддерживаемый вид динамических крейтов — cdylib, но это используется для создания плагинов с C-совместимым внешним интерфейсом, а не для «родных» динамических библиотек. * За замечание спасибо @mzabaluev

пятница, 24 января 2020 г.

Язык Rust: почему внешняя с++-функция выполняется в другом потоке?

#rust


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

Почему так происходит? Разве так задумано? Это описано где-нибудь?

Вот тестовая программа на Rust:

extern crate libc;
use libc::{c_int};

#[link(name = "Project1", kind="static")]
extern {
    pub fn lib_fun(i: c_int) -> c_int;
}


fn main() {
    unsafe { lib_fun(2); }
    println!("from Rust: {}", 2);
}


Вняшняя функция lib_fun на С++ выглядит так:

#include 

extern "C" {
    int lib_fun(int t) {
        printf("from C++: %d\n", t);
        return t;
    }
}


Ожидаю, что программа должна вывести:

from C++: 2
from Rust: 2


Однако при запуске она выводит в обратной последовательности:

from Rust: 2
from C++: 2


Получается, внешняя функция lib_fun выполняется в другом потоке. Или я не прав?

Система Windows 7x64
    


Ответы

Ответ 1



Она пишет в стандартный вывод через свой буфер. Вставка в С++-функции fflush(stdout); после printf решило проблему.

Не собирается второе издание книги “Язык программирования Rust”

#rust


В сеть выложили второе издание популярной книги “Язык программирования Rust” (https://github.com/ruRust/rust_book_2ed).
Там же и инструкция, как собрать книжку из заготовок, с помощью mdbook build.  Однако
- не получается! При выполнении этой команды вадаются сообщения:

(mdbook::utils): Error: Summary parsing failed
(mdbook::utils):    Caused By: There was an error parsing the numbered chapters
(mdbook::utils):    Caused By: Error at line 127, column 31: You can't have an empty
link. 


Я полный профан в Rust, для того и книжку пытаюсь получить - что бы прочитать. Может
мне кто-то подсказать, в чём тут проблема?

Да, при первом запуске mdbook build выдалось длинное сообщение про то, что файл book.toml
старого формата и его необходимо подправить. Я исправил, в соответствии с рекомендациями.
Получилось вот так:

[book]
title = "The Rust Programming Language"
author = "Steve Klabnik and Carol Nichols, with Contributions from the Rust Community"

[build]
    [build-dir]
    destination = './build' 

    


Ответы

Ответ 1



Действительно не собирается. Но вопрос можно решить. Не собирается потому, что в файле src/SUMMARY.md не прописано несколько линков. Поступаем следующим образом: В каталоге src создаем файл dummy.md примерного содержания ## Это файл-заглушка В документации не хватало пару файлов и книга не собиралась. Для этого делаем файл-заглушку. В файле src/SUMMARY.md в самом конце прописываем линки на этот файл ... - [Appendix](appendix-00.md) - [A - Keywords](appendix-01-keywords.md) - [B - Operators](appendix-02-operators.md) - [C - Derivable Traits](dummy.md) - [D - Nightly Rust](dummy.md) - [E - Macros](dummy.md) - [F - Translations](dummy.md) - [G - Newest Features](appendix-07-newest-features.md) Все, после этого сборка проходит нормально.

четверг, 23 января 2020 г.

Ввод данных с клавиатуры - Rust

#input #rust


Здравствуйте! Как я могу считать данные вводимые пользователем с клавиатуры? Здесь
также задан вопрос, но предложенные способы очень неудобны. Можно ли как-то считать
строку в стиле Python (input()) или C++/Си (cin/scanf())? А если нет, то как сделать
это более удобным способом?

UPD

Нашел это. Подключаю к проекту:  

[dependencies.text_io]
version = "*"
features = ["nightly"]


Но к сожалению уверенно отказывается компилироваться quasi v0.3.0  
    


Ответы

Ответ 1



Написал небольшую функцию: fn read_string(comment:&str) -> String { println!("{}", comment); let mut string: String = String::new(); std::io::stdin().read_line(&mut string) .ok() .expect("Error read line!"); return string; } Если вместо return string написать return string.trim().parse::().unwrap(); можно получить тип i32, а по аналогии - все остальные. Теперь можно писать так: let number:i32 = read_i32("Input number: "); Пока не знаю, как выполнить чтение на одной строке с желаемым тектом, т.е при использовании примера выше: Input number: (здесь вводится текст) UPD Чтобы текст вводился на одной строке с комментарием к нему модифицируйте функцию: use std::io::Write; use std::io; fn read_string(comment:&str) -> String { print!("{}", comment); io::stdout().flush(); let mut string: String = String::new(); io::stdin().read_line(&mut string) .ok() .expect("Error read line!"); return string; }

воскресенье, 12 января 2020 г.

Реализация внешнего trait для внешнего шаблона, параметризованного локальным типом в Rust

#rust


После перехода с январьской версии компилятора Rust на beta. Обнаружил что код подобный
этому не компилируется http://is.gd/oX7GWN:

use std::fmt::{Display, Formatter, Error};

struct MyLocalType;

type MyResult = Result;

impl Display for MyResult {
    fn fmt(&self, f: &mut Formatter) -> Result<(), Error> {
        f.write_str("some test string")
    }
}

fn main() { 
    let r: MyResult = Ok(MyLocalType); 
    println!("{}" , r); 
}


Это ошибка компилятора, или теперь внешние параметризованные типы нужно всегда "заворачивать"
в свои ?
    


Ответы

Ответ 1



Ответ, скопированый с англоязычного stackoverflow: https://stackoverflow.com/questions/29789877/can-not-implement-trait-from-another-crate-for-generic-type-from-another-crate-p There's no direct way to solve this for a pure alias like type. The code is the same as impl Display for Result and the compiler can't ensure that there will be no conflicting implementations in other crates (aka, can't ensure that the implementation is 'coherent'). Being able to do it is definitely useful sometimes, but it was unfortunately a bug that the compiler accepted it before. Solutions include: defining a proper wrapper type for Result, e.g. struct MyResult(Result);, defining your own enum: enum MyResult { Ok(MyType), Err(String) }, define a wrapper type, but only use it when printing, i.e. write println!("{}", Wrapper(r)); instead of println!("{}", r);. Both of these make MyResult a local type, and so the impl then should be legal. Описание на русском, как его понял я: Теперь нет способа реализовывать типаж для стороннего обобщённого типа, пусть даже и параметризованного локальным типом. Так как компилятор не может быть уверен, что реализация будет согласована со всеми другими реализациями. Чтобы решить эту проблему нужно сделать тип полностью локальным. Eсть несколько способов: «Обернуть» его в локальный тип-обёртку, например: struct MyResult(Result); Объявить свой собственный Result-подобный тип: enum MyResult { Ok(MyType), Err(String) } Или же обернуть его, но использовать обёртку только там, где необходим требуемый типаж, например: println!("{}", Wrapper(r)); вместо println!("{}", r);

Аналог функции system из C++ для Rust

#cpp #linux #rust


Есть ли в Rust аналог функции system из C++? Или что-то подобное, позволяющее посылать
команды командному процессору Linux.
    


Ответы

Ответ 1



use std::process::Command; let status = Command::new("ls").status().unwrap_or_else(|e| { panic!("failed to execute process: {}", e) }); println!("process exited with: {}", status); Документация std::process::Command Однако в винде это не сработает. Там можно так: use std::process::Command; let status = Command::new("cmd.exe").arg("/c").arg("dir").status().unwrap_or_else(|e| { panic!("failed to execute process: {}", e) });

Как правильно указать время жизни ссылки в реализации трэйта

#rust


Как реализовать TraitFoo для структуры Foo?

#[derive(Debug)]
struct Foo<'f>{
    os: Option<&'f str>
}

impl<'f> Foo<'f> {
    fn new(x:&'f str) -> Foo<'f>{
        Foo{
            os:Some(x)
        }       
    }
}

trait TraitFoo {
    fn foo(x:&str) -> Self;
}

impl<'f> TraitFoo for Foo<'f>{
    fn foo(x:&str) -> Foo<'f> {
        Foo{
            os:Some(x)
        }
    }
}




fn main() {
    println!("{:?}", Foo::new("one"));
    println!("{:?}", Foo::foo("two"));
}


Этот код компилится с ошибкой:

 G:\Work\Rust\test01\src\lf_trait.rs:21:12: 21:13 error: cannot infer an appropriate
lifetime for automatic coercion due to conflicting requirements
G:\Work\Rust\test01\src\lf_trait.rs:21          os:Some(x)
                                                        ^
G:\Work\Rust\test01\src\lf_trait.rs:19:2: 23:3 help: consider using an explicit lifetime
parameter as shown: fn foo(x: &'f str) -> Foo<'f>
G:\Work\Rust\test01\src\lf_trait.rs:19  fn foo(x:&str) -> Foo<'f> {
G:\Work\Rust\test01\src\lf_trait.rs:20      Foo{
G:\Work\Rust\test01\src\lf_trait.rs:21          os:Some(x)
G:\Work\Rust\test01\src\lf_trait.rs:22      }
G:\Work\Rust\test01\src\lf_trait.rs:23  }


Если добавить 'f в функции foo() то получим другую ошибку:

G:\Work\Rust\test01\src\lf_trait.rs:19:2: 23:3 error: method `foo` has an incompatible
type for trait: expected bound lifetime parameter , found concrete lifetime [E0053]
G:\Work\Rust\test01\src\lf_trait.rs:19  fn foo(x:&'f str) -> Foo<'f> {
G:\Work\Rust\test01\src\lf_trait.rs:20      Foo{
G:\Work\Rust\test01\src\lf_trait.rs:21          os:Some(x)
G:\Work\Rust\test01\src\lf_trait.rs:22      }
G:\Work\Rust\test01\src\lf_trait.rs:23  }


Перепробовал еще кучу вариантов. Объясните как это правильно сделать.
    


Ответы

Ответ 1



Вот ответ с английского форума от llogiq: Нужно указать время жизни в определении трэйта trait TraitFoo<'a> { fn foo(x: &'a str) -> Self; } impl<'a> TraitFoo<'a> for Foo<'a> { fn foo(x:&'a str) -> Foo<'a> { Foo{ os:Some(x) } } }

вторник, 7 января 2020 г.

Как проще всего считать матрицу(f64) из файла на Rust?

#файлы #матрицы #rust


Содержимое файла, матрица квадратная, в первой строке после # указан размер.

#3
1.1 -0.2 0.1
0.1 -1.2 -0.2
0.2 -0.1 1.1


Примерно так я бы считал ее на Си.

double **A;
int i,j,size=0;
FILE *f=NULL;

f=fopen("input.txt","w");
fscanf(f,"#%d\n",&size);
A=(double**)malloc(size*sizeof(double*));
for(i=0;i


Ответы

Ответ 1



Перевод моего кода с Си на Rust use std::fs::File; use std::io::{BufRead, BufReader}; fn main() { // open the file let mut f = BufReader::new(File::open("input.txt").unwrap()); // read the first line and extract the number from it let mut num_line = String::new(); f.read_line(&mut num_line).unwrap(); let n: usize = num_line[1..].trim().parse().unwrap(); // preallocate the array and read the data into it let mut arr = vec![vec![0f64; n]; n]; for (i, line) in f.lines().enumerate() { for (j, number) in line.unwrap().split(char::is_whitespace).enumerate() { arr[i][j] = number.trim().parse().unwrap(); } } println!("{:?}", arr); } Более характерный код для Rust use std::fs::File; use std::io::{BufRead, BufReader}; fn main() { let mut f = BufReader::new(File::open("input.txt").unwrap()); let mut num_line = String::new(); f.read_line(&mut num_line).unwrap(); let n: usize = num_line[1..].trim().parse().unwrap(); let arr: Vec> = f.lines() .take(n) .map(|l| l.unwrap().split(char::is_whitespace) .take(n) .map(|number| number.parse().unwrap()) .collect()) .collect(); println!("{:?}", arr); } Без учета размерности, указанной в первой строке use std::fs::File; use std::io::{BufRead, BufReader}; fn main() { let mut f = BufReader::new(File::open("input.txt").unwrap()); let mut s = String::new(); f.read_line(&mut s).unwrap(); let arr: Vec> = f.lines() .map(|l| l.unwrap().split(char::is_whitespace) .map(|number| number.parse().unwrap()) .collect()) .collect(); println!("{:?}", arr); }

Функция, возвращающая значения разных типов

#функции #типы_данных #rust


Как можно написать функцию, которая при определенных условиях возвращает значения
разных типов?

Например:

fn function(type_var:&str) {
   if type_var == "i32" {
      return 50i32;
   } else if type_var == "f64" {
      return 50.0;
   } else {
      return '50';
   }

    


Ответы

Ответ 1



В Rust строгая типизация, поэтому тип возвращаемого значения должен быть какой-то один. Тем не менее, можно создать тип-перечисление с различными типами вложенных значений в вариантах: enum ParsedValue { Int32(i32), Float(f64), String(String) } (последний вариант типа String с динамическим выделением и копированием строки, чтобы не отвлекаться на параметры времени жизни; если нужен пример не-аллоцирующей функции, могу развить). Функция в примере будет выглядеть так: fn function(type_var: &str) -> ParsedValue { if type_var == "i32" { ParsedValue::Int32(50) } else if type_var == "f64" { ParsedValue::Float(50.0) } else { ParsedValue::String(String::from("50")) } } По значению ParsedValue все равно придется делать что-нибудь вроде match, чтобы деструктурировать значение варианта, но этот match обязан быть исчерпывающим.

суббота, 4 января 2020 г.

Обработка ошибок в Rust

#rust


Есть программа на rust, взятая отсюда: Учебник по Rust

Захотелось мне, чтобы программа постоянно выводила собственный префикс ("guess_game>>
"). По-этому немного пришлось подкорректировать строки перед запросом ввода.

В оригинале было так

println!("Пожалуйста, введите предположение.");


Если просто заменить println! на print!, у меня вывод сбрасывался на терминал слишком
поздно. Выход был найден в принудительном вызове stdout().flush(). Но, когда я добавил
обработку ошибок, как приведена ниже, при запуске программы стали бесконечно рисоваться
строки "guess_game>> try: ". Прошу объяснить, почему так и какова логика?

extern crate rand;

use std::io::stdin;
use std::io::Write;
use std::io::stdout;
use rand::Rng;
use std::cmp::Ordering;

fn main() {
    let snum = rand::thread_rng().gen_range(1, 101);
    println!("guess_game>> rnd: {}", snum);

    loop {
        let mut guess = String::new();

        print!("guess_game>> try: ");
        match stdout().flush() {
            Ok(_)  => continue,
            Err(_) => continue,
        };

        stdin().read_line(&mut guess)
               .ok()
               .expect("Fail read line");

        let guess: u32 = match guess.trim().parse() {
            Ok(num) => num,
            Err(_)  => continue,
        };

        match guess.cmp(&snum) {
            Ordering::Less    => println!("guess_game>> Less"),
            Ordering::Equal   => {
                println!("guess_game>> Equal");
                break;
            }
            Ordering::Greater => println!("guess_game>> Greater"),
        }
    }
}


UPD:
Решение:

print!("guess_game>> try: ");
match stdout().flush() {
    Ok(_)  => (),
    Err(_) => (),
};

    


Ответы

Ответ 1



У вас бесконечно выводится guess_game>> try: по тому, что у вас получился бесконечный цикл.

Ответ 2



Более удобный способ проигнорировать ошибку в Result - превратить его в Option: stdout().flush().ok(); Однако, с точки зрения надежности, игнорировать ошибки вывода все же не стоит. Чтобы получить панику с информацией об ошибке, можно воспользоваться методом unwrap: stdout().flush().unwrap();

среда, 1 января 2020 г.

Знак вопроса RUST

#rust


fn main() {
use std::fs::File;
use std::io::prelude::*;

fn foo() -> std::io::Result<()> {
     let mut file = File::create("foo.txt")?; //здесь
     file.write_all(b"Hello, world!")?; //здесь 
     Ok(())
   }
}


Для чего нужен знак вопроса ?
    


Ответы

Ответ 1



expression? - это сокращенная запись для try!(expression). Описание макроса try! из руководства по Rust. Вкратце. Если expression возвращает значение Err(err), то try! (или ?) выполняет немедленный выход из функции: return Err(err.into()). Если же значение expression - Ok(v), то макрос возвращает v и исполнение продолжается.

вторник, 31 декабря 2019 г.

Как из String сделать &str (возникает ошибка времени жизни ссылки)

#rust


Написал функцию:

fn read_str<'a>() -> &'a str {
    let mut input = String::new();
    io::stdin().read_line(&mut input).ok();
    let output : &str = &input[..];
    &output
}


При компиляции возникает ошибка

src/main.rs:13:26: 13:31 error: `input` does not live long enough
src/main.rs:13     let output : &str = &input[..];
                                        ^~~~~
src/main.rs:10:30: 15:2 note: reference must be valid for the lifetime 'a as defined
on the block at 10:29...
src/main.rs:10 fn read_str<'a>() -> &'a str {
src/main.rs:11  let mut input = String::new();
src/main.rs:12  io::stdin().read_line(&mut input).ok();
src/main.rs:13     let output : &str = &input[..];
src/main.rs:14     &output
src/main.rs:15 }
src/main.rs:11:32: 15:2 note: ...but borrowed value is only valid for the block suffix
following statement 0 at 11:31
src/main.rs:11  let mut input = String::new();
src/main.rs:12  io::stdin().read_line(&mut input).ok();
src/main.rs:13     let output : &str = &input[..];
src/main.rs:14     &output
src/main.rs:15 }


Пробовал брать строку io::stdin().read_line(&mut input).ok(); в фигурные скобки,
дабы ограничить &mut, но это не помогает.
    


Ответы

Ответ 1



Вернуть из функции можно только ссылку на статический объект (с временем жизни 'static) или ссылку, которую вы получили во входных параметрах. Вы пытаетесь вернуть ссылку на объект созданный в стеке функции. После завершения функции все объекты в стеке будут уничтожены. Rust не позволит создать ссылку на объект, который она может пережить. Использование после освобождения (use after free) В вашем случае лучше всего вернуть String. Накладные расходы в этом случае невелики, так как почти вся информация внутри строки хранится в куче. Большинство проблем начинающих писать на Rust связаны с непониманием назначения различных типов указателей. Если не вдаваться в подробности, их там можно насчитать пять штук. Ссылки в Rust используются для передачи данных без передачи права владения. Например если у вас есть структура, в которой хранится строка: struct Person{ name:String } то выдавать информацию о ее содержимом лучше так: impl Person{ pub fn get_name(&self)->&str{ &self.name } } или можно вернуть копию строки: pub fn get_name_clone(&self)->String{ self.name.clone() } Но это приведет к дополнительным расходам на копирование строки. Расходы здесь возникают именно из-за копирования - вызова метода clone(), который создает вторую копию данных строки в куче, а не из-за того, что функция возвращает строку. Можно вернуть строку из структуры без копирования: pub fn get_name_own(self)->String{ self.name } Но после вызова этого метода исходный объект Person перестанет существовать, так как право владения было передано. Еще одна распространенная причина проблем - попытка хранить ссылки на другие объекты в структуре. Ссылки накладывают кучу ограничений на время жизни и мутабельность связанных объектов. Прежде чем это делать, стоит дважды подумать о возможности использования других типов указателей.

воскресенье, 29 декабря 2019 г.

В чем разница между loop и while true?

#while #rust


В чем разница между loop и while true в rust?


  В документации написано что:

loop {}

  
  Интерпретируется по другому компилятором чем:

 while true {}

  
  Хотел бы понять в чем эта разница и на сколько плохо использовать не loop, а while true


версия вопроса на Английском от @aij
    


Ответы

Ответ 1



На такой вопрос был дан ответ в Reddit. Разница заключается в том, что в случае while true все переменные должны быть инициализированы до цикла, а в случае loop это не обязательно: Пример с loop let x; loop { x = 1; break; } println!("{}", x) этот код отлично работает, однако следующий let x; while true { x = 1; break; } println!("{}", x); покажет ошибку компиляции "use of possibly uninitialised variable" с указанием на x в println. Во втором случае компилятор не определяет, что тело цикла всегда выполнится хотя бы один раз. перевод ответа @telotortium

Ответ 2



Сейчас есть еще одно отличие - из loop можно вернуть значение: let x; let y = loop { x = 1; break 42;}; println!("x={} y={}", x, y); Returning from loops

пятница, 27 декабря 2019 г.

Почему параллельный map работает на порядки медленнее последовательного?

#распараллеливание #rust


Хотел поэкспериментировать с параллельностью в Rust, в частности - сравнить последовательный
map из стандартной библиотеки и параллельный map из rayon. Вот код бенчмарка:

#![feature(test)]

extern crate test;
extern crate rand;
extern crate rayon;

#[cfg(test)]
mod tests {
    use rayon::prelude::*;
    use test::Bencher;

    #[bench]
    fn iter_test(b: &mut Bencher) {
        let vec: Vec = (0..20000).collect();

        b.iter(|| -> i64 {
            vec.iter().map(|x| x * x).sum()
        });
    }

    #[bench]
    fn par_iter_test(b: &mut Bencher) {
        let vec: Vec = (0..20000).collect();

        b.iter(|| -> i64 {
            vec.par_iter().map(|x| x * x).sum()
        });
    }
}


Результаты:

running 2 tests
test tests::iter_test     ... bench:       8,061 ns/iter (+/- 2,606)
test tests::par_iter_test ... bench:   1,915,544 ns/iter (+/- 40,786,648)


Почему параллельный map настолько медленный? Я конечно понимаю, что на распараллеливание
какие-то ресурсы тратятся, но не столько же. Может последовательный map как-то компилятор
оптимизирует? Как тогда написать бенчмарк, на примере которого можно будет почувствовать
преимущество параллельного map?

P. S. Пробовал под передавать в map рекурсивный факториал - результаты отличаются
не так сильно, но последовательный map все равно быстрее.
    


Ответы

Ответ 1



Потому что накладные расходы на распараллеливание на порядки превышают полезную нагрузку в данном случае. На моей машине (Intel(R) Core(TM) i7-4770 CPU @ 3.40GHz) параллельная версия начинает обгонять на 16 000 000 значений, а на 32 000 000 обгон достигает ~25% и дальше уже сильно не растёт mkpankov@mkpankov-OptiPlex-9020 /tmp/rtest [master *] ± % ➜ cargo bench # 16000000 Compiling rtest v0.1.0 (file:///tmp/rtest) Finished release [optimized] target(s) in 0.70 secs Running target/release/deps/rtest-14d3cc84caf097ae running 2 tests test tests::iter_test ... bench: 8,749,482 ns/iter (+/- 465,264) test tests::par_iter_test ... bench: 7,700,212 ns/iter (+/- 2,354,146) test result: ok. 0 passed; 0 failed; 0 ignored; 2 measured mkpankov@mkpankov-OptiPlex-9020 /tmp/rtest [master *] ± % ➜ cargo bench # 32000000 Compiling rtest v0.1.0 (file:///tmp/rtest) Finished release [optimized] target(s) in 0.70 secs Running target/release/deps/rtest-14d3cc84caf097ae running 2 tests test tests::iter_test ... bench: 17,497,810 ns/iter (+/- 765,301) test tests::par_iter_test ... bench: 13,826,826 ns/iter (+/- 1,615,362) test result: ok. 0 passed; 0 failed; 0 ignored; 2 measured mkpankov@mkpankov-OptiPlex-9020 /tmp/rtest [master *] ± % ➜ cargo bench # 64000000 Compiling rtest v0.1.0 (file:///tmp/rtest) Finished release [optimized] target(s) in 0.71 secs Running target/release/deps/rtest-14d3cc84caf097ae running 2 tests test tests::iter_test ... bench: 34,855,214 ns/iter (+/- 1,047,213) test tests::par_iter_test ... bench: 27,347,082 ns/iter (+/- 236,451) test result: ok. 0 passed; 0 failed; 0 ignored; 2 measured

воскресенье, 22 декабря 2019 г.

Как покрасивее написать обработку ошибок в Rust?

#rust


Есть примерно вот такой кусок кода парсера сайта с использованием select:

extern crate select;

use select::document::Document;
use select::predicate::{Class, Name, And};

fn main() {
    // Пример; реальность чуть сложнее, но не суть
    let html = "
+15.00
"; let page = Document::from(html); let blog_id: u32 = page.find(And(Name("div"),Class("vote-item"))) .find(Name("span")).first() .unwrap().attr("id") .unwrap().split('_').collect::>().last() .unwrap().parse::().unwrap(); println!("Blog id: {}", blog_id); } Я попытался его переписать так, чтобы на выходе получить Option (который потом переведу в Result; конкретно здесь сохранение точной причины ошибки не интересует). Результат работает, но выглядит, мягко говоря, не очень: let blog_id: Option = page.find(And(Name("div"),Class("vote-item"))).first() .and_then(|x| x.find(Name("span")).first()) .and_then(|x| x.attr("id").and_then(|x| Some(x.to_string()))) .and_then(|x| x.split('_').collect::>().last().and_then(|x| Some(x.to_string()))) .and_then(|x| x.parse::().ok()); match blog_id { Some(i) => println!("Blog id: {}", i), None => println!("Cannot parse blog_id"), }; Возможно ли оформить это как-нибудь более красиво? Если я пытаюсь это как-нибудь упрощать (особенно противный to_string), компилятор незамедлительно начинает ругаться на времена жизни, заимствования и прочую дребедень. Использование match или if let, подозреваю, приведёт к зашкаливающему количеству лесенок, а если их избегать, то красивость всё равно не особо увеличится. Ещё я пытался написать макрос с циклом, эквивалентный постоянным вызовам and_then, но получилось так же некрасиво и to_string никуда не делись. В идеале что-нибудь похоже на такой Python-эквивалент: try: blog_id = int(page.find("div", {"class": "vote-item"}).\ find("span")[0].\ get("id").\ rsplit("_")[-1]) except Exception: blog_id = None (наверно, лучше просто взять xpath, но вопрос пока не про это :)


Ответы

Ответ 1



Если в результате (в возвращаемом значении функции) нужно получить Result, для улучшения читабельности кода можно воспользоваться макросом try!. Любое значение Option, полученное из операций библиотеки select, можно преобразовать в Result методом ok_or, с типом ошибки на ваш выбор, но таким, чтобы он преобразовывался (трейтами Into/From) в тип ошибки для Result, возвращаемого функцией, которая содержит ваш код с использованием try!. fn get_blog_id(page: &Document) -> Result { let item = try!(page.find(And(Name("div"),Class("vote-item"))).first() .ok_or(MyDocError::NoVoteItem))); let id = try!(item.attr("id") .ok_or(MyDocError::NoIdAttr))); ... } Мне непонятно, в чем проблема со временем жизни заимствований в цепочке and_then, но изложенный выше подход позволит этого избежать, поскольку промежуточные значения привязаны к слотам на время вызова содержащей функции и из них можно заимствовать отрезки строк (AKA slices). Возможный недостаток такого подхода в том, что значения-ошибки в параметрах вызовов ok_or создаются в любом случае, так что если тип ошибки имеет нетривиальную инициализацию и/или деструктор (например, создает String), это создает ненужную работу даже в случае успешного выполнения. Чтобы этого избежать, можно воспользоваться ok_or_else и спрятать инициализацию ошибки в лямбда-выражение, но это несколько ухудшит эстетическое качество кода. Добавлено: Библиотеки макросов try_or и try_opt позволяют написать код раннего возврата Option или Result, подобный приведенному выше, более компактно. В последних версиях языка использование стандартного макроса try! можно заменить на оператор ?.

Ответ 2



Начиная с Rust версии 1.22, оператором ? можно пользоваться и для быстрого возврата Option::None: fn get_blog_id(page: &Document) -> Option { let item = page.find(And(Name("div"), Class("vote-item"))).first()?; let id = item.attr("id")?; // ... }

Ответ 3



Сейчас как-то сошлись на использовании макроса hado, который похож на упомянутый в комментариях mdo, но, по мнению некоторых, немножко лучше: hado! { el <- page.find(And(Name("div"),Class("vote-item"))).find(Name("span")).first(); id_s <- el.attr("id"); num_s <- id_s.split('_').last(); num_s.parse::().ok() } По сути он делает то же самое, что и мой код с and_then: для Some вычисляет следующее выражение, а None оставляет как есть, и в результате отдаёт Option — но с более красивым синтаксисом. (Проблем с временем жизни почему-то не возникло.) (Подобно mdo, юзает многочисленные вложенные друг в друга Monad::bind, но пока каких-либо плохих последствий этого не замечено.)

Ответ 4



Хм, unwrap говорите не нравится. Давайте посмотрим что мы имеем. try!: Я давно запреметил один макрос, называется try!, суть его крайне проста. Если есть ошибка то верни Result<..,..> на fn, если ошибки нет верни обьект. Принцип как у unwrap. macro_rules! try { ($e:expr) => (match $e { Ok(val) => val, Err(err) => return Err(err), }); } Описание: https://doc.rust-lang.org/std/macro.try.html Код взят. fn write_to_file_using_try() -> Result<(), MyError> { //Result требуется обязательно реализовывать, так как вы используете try let mut file = try!(File::create("my_best_friends.txt")); //если все ок то верни обьект, если нет, заверши функцию и верни Result try!(file.write_all(b"This is a list of my best friends.")); //если все ок то все ок, если нет, заверши функцию и верни Result println!("I wrote to the file"); Ok(()) } Также есть оператор ? не сталкивалось с ним работать. Prefer using ? syntax to try!. ? is built in to the language and is more succinct than try!. It is the standard method for error propagation. unwrap Думаю вы с ним уже познакомились, его код описывается зачастую так fn unwrap(self) -> T { match self { Option::Some(val) => val, //если обьект относится к enum Option::Some(e) значит он не null и достань значение из него и верни мне Option::None => //если обьект относится к enum Option::None значит он относится к нулу и значения никакого мы не получили panic!("я упал"), } } Есть еще множество способов описания, но они у меня не работают(бо неизведано кто их реализовывает) http://m4rw3r.github.io/rust-questionmark-operator Через IF (работает через что захочется, Option, Result, ...) Перепишу код примера на IF fn write_to_file_using_try() -> bool { if let Ok(mut file) = File::create("my_best_friends.txt") { if let Ok(_e) = file.write_all(b"This is a list of my best friends."){ println!("I wrote to the file"); return true; } } false } Еще пример на IF из моего кода pub fn save(&self) -> bool { { let lock = self.save_file.lock().unwrap(); if let Some(ref file_s) = *lock { if let Ok(file_f) = File::create(file_s){ let mut file = BufWriter::new(file_f); let end_w = [10u8; 1]; let locker = self.arr.lock().unwrap(); for i in locker.iter(){ file.write(i.as_bytes()).unwrap(); file.write(&end_w); } return true; } } } self.warning("no save file..."); false }