LINUX.ORG.RU

Implicit function arguments в Rust

 


1

9

Привет, ЛОР.

Расскажи мне, как в Rust принято таскать read-only данные типа конфигурации? В идеале я хочу аналог ReaderT из Haskell, т.к. он позволяет легко тестировать код без свистоплясок глобальными переменными и прочих странных вещей. Для Rust я похожего не нашёл.

Неужели нужно городить глобальные переменные либо руками каждой фукнции писать лишний аргумент?

★★★★★

Последнее исправление: hateyoufeel (всего исправлений: 2)

Можно сделать ещё вариант для слегка двинутых:

static mut _config: i32 = 0;
static mut _config_fin: bool = false;

fn setconfig(n: i32) {
    unsafe {
        if _config_fin {
            panic!("double set of config");
        }
        _config = n;
        _config_fin = true;
    }
}

fn config() -> i32 {
    unsafe {
        if _config_fin == false {
            panic!("config is not initialized");
        }
        _config
    }
}

Deleted
()
Ответ на: комментарий от hateyoufeel

Только этот самый struct не обязательно opaque, и он будет передаваться неявно вниз по стеку вызовов.

А ля динамические переменные в CL?

korvin_ ★★★★★
()
Ответ на: комментарий от theNamelessOne

А разве без HKT можно сделать нормальные монады?

Reader можно сделать. Кстати, есть do-нотация на макросах под Rust, там поддерживаются Option, Result и итераторы.

https://github.com/TeXitoi/rust-mdo

hateyoufeel ★★★★★
() автор топика
Ответ на: комментарий от kirk_johnson

Если в решении проблемы не задействованы хотя-бы два класса или три монады — то это плохой фортранный|сишный код и о нём надо забыть?

Deleted
()
Ответ на: комментарий от hateyoufeel

Кстати, есть do-нотация на макросах под Rust, там поддерживаются Option, Result и итераторы.

Ну и фишка в том, я понимаю, что свою монаду туда не запихнуть.

theNamelessOne ★★★★★
()
Ответ на: комментарий от theNamelessOne

Ну и фишка в том, я понимаю, что свою монаду туда не запихнуть.

Можно запихнуть. Просто компилятор не гарантирует, что она будет иметь такой же интерфейс. Там на макросах всё построено, без трейтов.

hateyoufeel ★★★★★
() автор топика
Ответ на: комментарий от hateyoufeel

Там на макросах всё построено, без трейтов.

Собственно, под «нормальными монадами» я имел в виду именно один трейт, а не кучу методов с одинаковыми названиями, реализованными для разных типов.

theNamelessOne ★★★★★
()

Чет я не догнал сходу, чем оно лучше синглтона?

pon4ik ★★★★★
()
Ответ на: комментарий от Deleted

Если в решении проблемы не задействованы хотя-бы два класса или три монады — то это плохой фортранный|сишный код и о нём надо забыть?

Если в 15 строках есть два unsafe блока и ни одного вызова FFI — да, скорее всего это плохой фортрнный код.

kirk_johnson ★☆
()
Ответ на: комментарий от hateyoufeel

Понятия не имею. Я довольно далёк от CL.

Ну, если я правильно понял,

(defstruct config protocol host port)

(defvar *config* nil)

(defun get-request (end-point)
  (format t "GET ~a://~a:~a/~a~%"
          (config-protocol *config*)
          (config-host *config*)
          (config-port *config*)
          end-point))

(defun main ()
  (get-request "customers")
  (get-request "orders"))



;;;; Test local

(let ((*config* (make-config
                 :protocol 'http
                 :host "localhost"
                 :port 3000)))
  (main))

;;;; Test remote

(let ((*config* (make-config
                 :protocol 'https
                 :host "152.12.210.3"
                 :port 8080)))
  (main))

=>

GET HTTP://localhost:3000/customers
GET HTTP://localhost:3000/orders
GET HTTPS://152.12.210.3:8080/customers
GET HTTPS://152.12.210.3:8080/orders
korvin_ ★★★★★
()
Ответ на: комментарий от kirk_johnson

Не вижу ничего плохого конкретно в этих двух unsafe блоках, ведь в сумме в них всего два примитивных выражения, безопасность которых очевидна. Ну если вы конечно не будите запускать несколько парсеров конфига паралельно, а если будите, то обернуть _config в Mutex будет довольно тривиально.

Deleted
()
Ответ на: комментарий от Deleted

Ну если вы конечно не будите запускать несколько парсеров конфига паралельно

Ты понимаешь, что вся идея rust в том, чтобы убрать подобные проблемы? Если мне все равно придется держать в голове всю эту мелкую чепуху, то почему бы мне просто не взять C, где многие вещи делаются проще?

kirk_johnson ★☆
()
Ответ на: комментарий от kirk_johnson

Ты понимаешь, что вся идея rust в том, чтобы убрать подобные проблемы?

Да, но Safe Rust недостаточно для реализации некоторых низких базовых вещей, поэтому это абсолютно нормально что кусочки Unsafe Rust присутствуют и в стандартной библиотеке и в таких вроде rayon.

держать в голове всю эту мелкую чепуху

Держать в голове две примитивных unsafe строки чтобы они были безопасными или держать в голове ~сотню~ строк макросов|трейтов чтобы в них не было логической ошибки.

Deleted
()
Ответ на: комментарий от Deleted

Да, но Safe Rust недостаточно для реализации некоторых низких базовых вещей, поэтому это абсолютно нормально что кусочки Unsafe Rust присутствуют и в стандартной библиотеке и в таких вроде rayon.

Ну тред примерно к этому и идет. Rust сам по себе недостаточно выразителен.

Держать в голове две примитивных unsafe строки чтобы они были безопасными или держать в голове ~сотню~ строк макросов|трейтов чтобы в них не было логической ошибки.

Мне кажется, что это не последние unsafe строки в проекте. Если уж мы даже конфиг без проблем передать не можем.

kirk_johnson ★☆
()
Ответ на: комментарий от Deleted

Ну если вы конечно не будите запускать несколько парсеров конфига паралельно

А если я хочу тесты параллельно запустить, подсунув каждому свой конфиг?

если будите, то обернуть _config в Mutex будет довольно тривиально.

И что мне mutex даст? Либо все потоки кроме одного будут висеть, либо все кроме одного будут читать левый конфиг. Ты молодец, хорошо придумал.

hateyoufeel ★★★★★
() автор топика

Зависит от ситуации.

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

Ну или держать в глобальном скопе конфиг, но встает проблема с инициализацией: либо с lazy_static! инициализировать чтением файла прямо там, либо инициализировать в другом месте и заворачивать всё в Arc<RwLock<Config>>

mersinvald ★★★★★
()
Ответ на: комментарий от Deleted

Не вижу ничего плохого

Твоя программа протаскивает Undefined Behavior из unsafe в безопасный раст. Эти две функции позволяют получить data race в безопасном коде, а это первый признак того что они написаны некорректно. Ты нарушаешь гарантии на которые опирается компилятор языка.

pftBest ★★★★
()
Ответ на: комментарий от hateyoufeel

Кстати, раз уж мы про C++ тут вспомнили, годный аналог будет выглядеть примерно так:

Прошу прощения за тормознутость, но не понял одну вещь. Допустим, есть такое:

void first(int a, const Config & config = get_conf()) {...}
void second(int a, int c, const Config & config = get_conf()) {...}
void int my_function(int a, int b, const Config& config = get_conf())
{
    if(config.addOrMultiply)
        return first(a) + first(b);
    else
        return second(a, b) * second(b, a);
}
Мне думается, что внутри my_function вы не можете просто вызывать first и second не передавая им явно config. Ведь кто-то мог вызвать my_function с другим экземпляром Config-а, но этот экземпляр не попадет в first и second. И единственным входом здесь видится явное протягивание config внутри my_function во все вложенные вызовы first и second.

А как эта проблема решается в Haskell?

eao197 ★★★★★
()
Ответ на: комментарий от eao197

ReaderT протягивает состояние по умолчанию, а вот менять его нужно явно.

first :: a -> ReaderT Config IO a
first a = do
    return ...

second :: a -> a -> ReaderT Config IO a
second a b = do
    return ...

myFunc :: a -> a -> ReaderT Config IO a
myFunc a b = do
    liftIO $ ... // Сделать что-то в IO монаде
    liftIO $ ... // Сделать ещё что-то в IO монаде
    config <- ask
    result <- 
        if some_condition config 
            // first использует ту же конфигурацию что и my_func
            then first ... 
            // меняем конфигурацию для second
            else local (changeConfigFunc) $ second ...
    return result

main = do
    ...
    config <- readConfigFromFile
    ...
    result <- runReaderT (myFunc ...) config

В коде могут быть ошибки. Давно с Haskell дела не имел.

red75prim ★★★
()
Последнее исправление: red75prim (всего исправлений: 1)
Ответ на: комментарий от red75prim

Т.е. по сути нужно иметь неявный локальный стек из объектов Config. С возможностью обратиться к верхнему Config-у в стеке, запихнуть еще один Config в стек (т.е. создать новую конфигурацию), автоматически убрать Config из стека при выходе из скоупа?

eao197 ★★★★★
()
Ответ на: комментарий от eao197

Config будет только один. Фактически, это синглтон. Откуда возьмется стек Config'ов?

config <- readConfigFromFile -- единожды читаем конфиг из файла

Virtuos86 ★★★★★
()
Ответ на: комментарий от Virtuos86

Config будет только один. Фактически, это синглтон. Откуда возьмется стек Config'ов?

Если Config один, то от меня ускользает смысл всего этого действа. Тогда достаточно иметь всего одну thread-local переменную типа Config и все.

Я думал, что смысл в том, что сперва мы получаем config1, запускаем с ним функцию f, внутри f создает config2, с которым запускает g, g внутри себя запускает e. И g, и e, работают с config2. При этом ни g, ни e не получают config2 явно через свои аргументы. Когда g и e возвращают управление f, то f продолжает работать с config1.

Вот для такого сценария имеет смысл париться с хитрыми реализациями. Если же config всегда один и может быть изменен, то зачем в императивном языке все эти ухищрения с модами-трансформерами и пр. HKT?

eao197 ★★★★★
()
Ответ на: комментарий от Deleted

unsafe нужно использовать только при крайней необходиомости. Вы же переносите проблемы сишки в rust. lazy_static и вперёд. Не намного больше кода будет, зато безопасно.

RazrFalcon ★★★★★
()
Ответ на: комментарий от hateyoufeel

Я правильно понимаю, что в этом случае обращение к переменной происходит по имени?

Не понял вопрос. А как ещё можно обращаться к переменной?

korvin_ ★★★★★
()
Ответ на: комментарий от eao197

Я думал, что смысл в том, что сперва мы получаем config1 [...] Когда g и e возвращают управление f, то f продолжает работать с config1.

Так всё и есть. Только config2 нужно создавать явно: f ... = do ... local createConfig2 $ g ....

Когда думаю о коде на Haskell, есть постоянное ощущение, что мозги выворачиваются наизнанку...

red75prim ★★★
()
Последнее исправление: red75prim (всего исправлений: 2)
Ответ на: комментарий от Deleted

Только lazy_static скачали 2,478,572 раз. Есть большая вероятность, что он работает лучше велосипеда.

Ну и реализовать Sync без unsafe в принципе нельзя.

First and foremost, they're unsafe traits. This means that they are unsafe to implement, and other unsafe code can assume that they are correctly implemented. Since they're marker traits (they have no associated items like methods), correctly implemented simply means that they have the intrinsic properties an implementor should have. Incorrectly implementing Send or Sync can cause Undefined Behavior.

RazrFalcon ★★★★★
()
Последнее исправление: RazrFalcon (всего исправлений: 1)
Ответ на: комментарий от RazrFalcon

Как всё просто оказывается. Ясно.

Deleted
()
Ответ на: комментарий от Deleted

unsafe значит, что это программист должен гарантировать невозможность создания UB или других проблем при использовании предоставляемого им (программистом) безопасного интерфейса в safe Rust.

lazy_static это гарантирует. get_config() -> T, set_config(_: T) - нет.

red75prim ★★★
()
Последнее исправление: red75prim (всего исправлений: 1)
Вы не можете добавлять комментарии в эту тему. Тема перемещена в архив.