Pull to refresh

Comments 42

Смотрю на захват переменных, вспоминаю log4shell и думаю: "Надеюсь, парсинга шаблонов {...} в рантайме и исполнения инъектированного кода в планах нет?"

В Rust println! и ему подобные принимают как шаблон только строковые литералы, такое кинет ошибку компиляции:

let fmt = "{fmt}";
println!(fmt);

Да, это знаю, просто как бы кому не пришла в голову "светлая" идея протащить это в рантайм для обычных строк (а не литералов, которые раздербаниваются на куски во время предкомпиляции)

В рантайм протащить это было бы сложновато. Как технически, так и организационно. Во‐первых, у Rust в runtime просто нет такой кучи информации, которую можно получить в той же Java, если её туда специально не добавит компилятор. Я примерно представляю, как это «специально добавит» должно выглядеть и оно содержит множество принципиальных проблем и вообще непросто в реализации.


Во‐вторых, пока даже на простенькие форматные строки с произвольными выражениями RFC не собрали — а там из проблем по сути только «как экранировать» и «а оно точно нужно?». Предложение вида «давайте потратим несколько человекомесяцев, чтобы получить в std свой собственный log4shell», конечно же найдёт широкую поддержку у разработчиков и сообщества.


Более ограниченный вариант вида «пользователь должен сам собрать словарь со всеми переменными, которые он хочет сделать доступными» ещё может появится в std и наверняка в каком‐то виде уже есть в крейтах. Но это не особо опасно.

Мне очень жалко, что фичу завезли только в макросах и в очень куцем виде. Я бы очень, очень хотел видеть полноценные f-строки.

А так уж ли это много даст? Это не так уж часто нужно, по-моему, а format! должен бы справиться во всех ситуациях, когда хочется f-строк, если я правильно понимаю.

А разве это вообще возможно в компилируемом языке?

Я сходу не представляю, как это сделать, не таща в каждый бинарник мини-компилятор или не навешивая на все переменные море мета-информации.

Мне кажется, вполне возможно.

Если я могу написать так:

[
  (expr1).to_string(),
  (expr2).to_sting(),
].join('')

То и компилятор может развернуть f-string в что-то подобное. Это же просто синтаксический сахар.

... а, я понял вопрос. Нет, речь не про eval для произвольных строк. Речь про поддержку f-нотации с строковыми литералами.

Чтобы можно было так: `f"Hello {username}! Now is {time.time()}!"`

Ну, в компайл-тайм - да.

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

Вот я бы хотел тут больше сахара, чтобы без макросов, а на уровне языка. В конце-концов, нам же насыпали сахара в районе for, который на Iter и IntoIter завязан.

А чем макрос плох? Они в расте достаточно гибкие, чтобы сделать почти все что угодно. А как обкатают фичу и если нужда будет - можно и в язык запилить, как с try! было

Много букв. Чем плох BEGIN END в языке?

FOR x IN range(0, 10).into_iter()
BEGIN

   PRINT(FORMAT!("{}", x));

END

Нравится?

Но позвольте, тут в итоге больше на один восклицательный знак..

println!("Hello, {person}!"); - это же сам println делает, будь это встроено в язык - что бы поменялось?

`println!("Hello, {person}! Now is {time.time()}");` не скомпилируется. Только чистый захват, без выражений. Что сделает условный {person.to_lower()} и т.д. недоступным.

Но это пока, вроде как ничего не мешает - концептуально - этому работать точно так же в макросе. Просто пока не сделали.

Допилить макрос, мне кажется, существенно проще, чем допилить компилятор.

Вот только этот макрос именно что реализуется компилятором:


    #[rustc_builtin_macro]
    #[macro_export]
    macro_rules! format_args_nl {
        ($fmt:expr) => {{ /* compiler built-in */ }};
        ($fmt:expr, $($args:tt)*) => {{ /* compiler built-in */ }};
    }

Почитал немного - в принципе, рассматривается и введение f-строк, причём скорее всего они будут просто синтаксическим сахаром к макросу format!

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

GraalVM даже при компиляции в нативный код тащит с собой jit-компилятор байт-кода, чтобы в рантайме оптимизировать то, что напрофилировал, плюс то, что при кодогенерации подъехало в classpath. А возможности для кодогенерации там обширные: агенты, ASM + CGLIB, LambdaMetaFactory и т.п.

Прост я не припомню нигде в компилируемых языках ничего подобного

C# же как минимум.

А если компилируемые в нативный код? C# всё-таки AOT-компилируется только в байткод, насколько я помню.

А какая разница? Что бы изменилось от компиляции в нейтив (тем более что при желании можно и так)?

Вот как раз AOT-компиляция идёт в машинный код, в отличии от JIT-компиляции.

оО А как там это работает?

Из документации:
// Composite formatting:
Console.WriteLine("Hello, {0}! Today is {1}, it's {2:HH:mm} now.", name, date.DayOfWeek, date);
// String interpolation:
Console.WriteLine($"Hello, {name}! Today is {date.DayOfWeek}, it's {date:HH:mm} now.");

If an interpolated string has the type string, it's typically transformed into a String.Format method call. The compiler may replace String.Format with String.Concat if the analyzed behavior would be equivalent to concatenation.

If an interpolated string has the type IFormattable or FormattableString, the compiler generates a call to the FormattableStringFactory.Create method.

Интересно.. т.е. это преобразование заменяет имена переменных в строке на позиционные аргументы для String.Format?

Нет.

Это в итоге вызовет WriteLine(string) или WriteLine(FormattableString) или другие варианты.

Во всяком случае не будет того вызова о, котором вы говорите.

А само форматированние строки претерпело уже несколько реинкарнаций: https://habr.com/ru/company/skillfactory/blog/599341/

Ну так а как значения-то подставлены будут?

Посмотрите статью, там всё сложно:)

Зависит от того, в какой тип попадает строка с интерполяцией.

Скажем в простом варианте:

string a="x", b = "y";

string c = $"{x}{y}";

Будет просто string.Concat(x,y)

В других вариантах будет посложнее.

Ага, ну то есть строка в компайл-тайм таки парсится? Только это делает особая компиляторная магия, специально сделанная для этого случая, а не макрос?

В Rust тоже магия компилятора.

macro_rules! format_args {

($fmt:expr) => {{ /* compiler built-in */ }};
($fmt:expr, $($args:tt)*) => {{ /* compiler built-in */ }};
}

https://github.com/rust-lang/rust/blob/master/library/core/src/macros/mod.rs#L842

Rust ещё немного не дорос до полноценных макросов :)
Вот пример макроса printf без магии компилятора: printf

Технически ничто не мешает реализовать эти макросы
как библиотечные, просто выигрыш неочевиден. А так есть, скажем, defmt.

Как же неочевиден.

Можно обновлять реализацию без изменения самого компилятора.

В идеале всё, что можно вынести в макросы в виде библиотек, а компилятор не менять.

Не очень в теме, но в расте стандартную библиотеку и компилятор не одни и те же люди делают?.. Версионируется оно точно вместе. В целом я согласен, что библиотечная реализация лучше, но в данном конкретном случае разница не кажется принципиальной. Плюс есть подозрения, что такая "магическая" реализация — это последствия поздней стабилизации макросов, когда раст 1.0 уже был, а свой format! написать ещё было нельзя.

Плюс есть подозрения, что такая "магическая" реализация — это последствия поздней стабилизации макросов, когда раст 1.0 уже был, а свой format! написать ещё было нельзя.

Скорее всего.


Вдобавок, вынос этого макроса в либу — это +1 процедурный макрос со всеми вытекающими.

Вдобавок, вынос этого макроса в либу — это +1 процедурный макрос со всеми вытекающими.

Честно говоря, не понял о каких последствиях речь. Реализация ведь и так есть, просто спрятана внутри компилятора, а не стандартной библиотеки. Если её перенести, то почему станет хуже?

Стоит понимать, что это не строка в привычном смысле, это скорей шаблон строки. Компилятор разбивает её на куски и ищет оптимальный способ склеить всё в итоговый результат.

В простейшем случае $"{foo} {bar}" будет заменено чем-то вроде String.Concat(foo, " ", bar).

Там дело в том, что format обходится без преобразования в строку. Он только берёт ссылки на переменные(dyn Display и прочие), а потому возникают вопросы из серии: где разместить результаты, чтобы оно не рассыпалось в любой нетривиальной ситуации.

Хотя, конечно, по большей части, там пока недоговорились о конкретном синтаксисе таких захватов(а-ля "а можно ли использовать строковые литералы и макросы?")

Библиотечное предложение №35 было одобрено для проверки в октябре 2021 года и расширяет применение #[must_use] в стандартной библиотеке. Оно покрывает больше функций, основной эффект которых — возвращение значения. Похоже на идею чистоты функций, но более слабо, чем настоящая языковая черта.

Нет, ничуть не похоже на идею чистоты функций.


На самом деле это похоже на линейные типы, особенно есть ещё и трейт Copy не добавлять.

Предположу, что логика такая: если на результате функции висит #[must_use], то она (скорее всего) чистая. Но да, сравнение слегка странное.

Sign up to leave a comment.

Articles