У меня иногда возникает состояние, которое я называю «Rust Fatigue».
Это когда тебе нужно что-то сделать, для тебя оно выглядит довольно логичным и простым, но в расте... это сделать почему-то слишком сложно. Либо легко, но... только в nightly.
Ситуация: мне не зашли растовые библиотеки парсинга yaml, ибо половина кривые и чего-нибудь да не поддерживают, половина под serde, где местами сложно парсить так как хочешь, что в итоге структуру yaml проще поправить, чем написать парсер того, каким хочешь этот yaml видеть. В итоге я взял unsafe-libyaml, которая является трансляцией сишной libyaml и поддерживает всё что мне нужно, сделал обёртку, всё ок (из фич что мне были нужны — возможность знать строку и колонку любого элемента, чтобы нормально ругаться на формат/ошибки конфига; плюс возможность распарситься в «дай мне чистую ноду, я сам по ней руками пройдусь»). Затем я решил сделать обёртку вокруг сериализации, чтобы в тестах можно было сравнивать expected результат описанный в ямле с тем что просериализую из actual результата.
В libyaml сериализация довольно логично сделана, стартуешь её процесс, скармливаешь либо документ целиком, либо можешь обойти какую-то свою структурку и поэмитить событий, вроде «а теперь начинается маппинг». И в начале можешь задать обработчик результата, являющийся функцией, которая принимает массив байт, которые затем можешь напечатать/куда-то записать/прочее.
Ну и я думаю: понятия не имею, куда эти байты захочу складывать, приму в функцию сериализации &mut impl Write, а там что передадут — туда и запишу. Но в качестве контекста коллбека выдачи итоговых байт мне нужно передать *mut libc::c_void, что простой указатель. Ну а &mut impl Write это один указатель плюс известный в компайлтайме тип. Я и думаю: кастану в &mut dyn Write, сделаю структурку, в которую мы положим два указателя — один на инстанс, другой на vtable или чего там нужно, чтобы вызывать методы у этого трейта.
Но не тут то было, нет сейчас явной возможности получить указатель на vtable, это nightly фича ptr_metadata которую делают с 2018 года. Вот так вот можно: https://play.rust-lang.org/?version=nightly&mode=debug&edition=2021&gist=d43d5275be1c6669de2a5d95f8d84c35
А в stable так нельзя и непонятно, чем именно заменить то. Пока писал этот пост подумалось, что нужно посмотреть, можно ли сконвертить impl Fn() в указатель на эту функцию, тогда по идее можно было бы сделать замыкание, которое умеет вызывать что нужно, указатель на замыкание был бы одним указателем и возможно сойдётся, но не факт. Кажется что не сойдётся, ибо тип замыкания это тоже странная штука довольно.
Попробую вечерком ещё поиграться с функциями, но если ничего не получится, то придётся таки принимать не &mut impl Write, а просто Vec и/или String, ну и зная конкретный тип там уже без проблем всё кастуется. И для каждого типа который захочется использовать — придётся либо копипастить, либо макросами обмазываться, раст победа.
#rust #мужчина_вы_что_не_видите_у_нас_обед
Это когда тебе нужно что-то сделать, для тебя оно выглядит довольно логичным и простым, но в расте... это сделать почему-то слишком сложно. Либо легко, но... только в nightly.
Ситуация: мне не зашли растовые библиотеки парсинга yaml, ибо половина кривые и чего-нибудь да не поддерживают, половина под serde, где местами сложно парсить так как хочешь, что в итоге структуру yaml проще поправить, чем написать парсер того, каким хочешь этот yaml видеть. В итоге я взял unsafe-libyaml, которая является трансляцией сишной libyaml и поддерживает всё что мне нужно, сделал обёртку, всё ок (из фич что мне были нужны — возможность знать строку и колонку любого элемента, чтобы нормально ругаться на формат/ошибки конфига; плюс возможность распарситься в «дай мне чистую ноду, я сам по ней руками пройдусь»). Затем я решил сделать обёртку вокруг сериализации, чтобы в тестах можно было сравнивать expected результат описанный в ямле с тем что просериализую из actual результата.
В libyaml сериализация довольно логично сделана, стартуешь её процесс, скармливаешь либо документ целиком, либо можешь обойти какую-то свою структурку и поэмитить событий, вроде «а теперь начинается маппинг». И в начале можешь задать обработчик результата, являющийся функцией, которая принимает массив байт, которые затем можешь напечатать/куда-то записать/прочее.
Ну и я думаю: понятия не имею, куда эти байты захочу складывать, приму в функцию сериализации &mut impl Write, а там что передадут — туда и запишу. Но в качестве контекста коллбека выдачи итоговых байт мне нужно передать *mut libc::c_void, что простой указатель. Ну а &mut impl Write это один указатель плюс известный в компайлтайме тип. Я и думаю: кастану в &mut dyn Write, сделаю структурку, в которую мы положим два указателя — один на инстанс, другой на vtable или чего там нужно, чтобы вызывать методы у этого трейта.
Но не тут то было, нет сейчас явной возможности получить указатель на vtable, это nightly фича ptr_metadata которую делают с 2018 года. Вот так вот можно: https://play.rust-lang.org/?version=nightly&mode=debug&edition=2021&gist=d43d5275be1c6669de2a5d95f8d84c35
А в stable так нельзя и непонятно, чем именно заменить то. Пока писал этот пост подумалось, что нужно посмотреть, можно ли сконвертить impl Fn() в указатель на эту функцию, тогда по идее можно было бы сделать замыкание, которое умеет вызывать что нужно, указатель на замыкание был бы одним указателем и возможно сойдётся, но не факт. Кажется что не сойдётся, ибо тип замыкания это тоже странная штука довольно.
Попробую вечерком ещё поиграться с функциями, но если ничего не получится, то придётся таки принимать не &mut impl Write, а просто Vec и/или String, ну и зная конкретный тип там уже без проблем всё кастуется. И для каждого типа который захочется использовать — придётся либо копипастить, либо макросами обмазываться, раст победа.
#rust #мужчина_вы_что_не_видите_у_нас_обед