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()}
и т.д. недоступным.
Но это пока, вроде как ничего не мешает - концептуально - этому работать точно так же в макросе. Просто пока не сделали.
Допилить макрос, мне кажется, существенно проще, чем допилить компилятор.
Почитал немного - в принципе, рассматривается и введение f-строк, причём скорее всего они будут просто синтаксическим сахаром к макросу format!
Ну, в принципе, конечно, сахарнее, но это не настолько большая разница, как с for'ом, где этот сахар реально кардинально меняет степень читаемости и довольно част.
GraalVM даже при компиляции в нативный код тащит с собой jit-компилятор байт-кода, чтобы в рантайме оптимизировать то, что напрофилировал, плюс то, что при кодогенерации подъехало в classpath. А возможности для кодогенерации там обширные: агенты, ASM + CGLIB, LambdaMetaFactory и т.п.
Прост я не припомню нигде в компилируемых языках ничего подобного
C# же как минимум.
А если компилируемые в нативный код? C# всё-таки AOT-компилируется только в байткод, насколько я помню.
оО А как там это работает?
// 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 процедурный макрос со всеми вытекающими.
В простейшем случае
$"{foo} {bar}"
будет заменено чем-то вроде String.Concat(foo, " ", bar)
.Ну вот вам такое в компилируемом языке:
https://docs.microsoft.com/en-us/dotnet/csharp/tutorials/string-interpolation
Там дело в том, что format обходится без преобразования в строку. Он только берёт ссылки на переменные(dyn Display и прочие), а потому возникают вопросы из серии: где разместить результаты, чтобы оно не рассыпалось в любой нетривиальной ситуации.
Хотя, конечно, по большей части, там пока недоговорились о конкретном синтаксисе таких захватов(а-ля "а можно ли использовать строковые литералы и макросы?")
Библиотечное предложение №35 было одобрено для проверки в октябре 2021 года и расширяет применение #[must_use] в стандартной библиотеке. Оно покрывает больше функций, основной эффект которых — возвращение значения. Похоже на идею чистоты функций, но более слабо, чем настоящая языковая черта.
Нет, ничуть не похоже на идею чистоты функций.
На самом деле это похоже на линейные типы, особенно есть ещё и трейт Copy не добавлять.
Rust 1.58.0: захватываемые идентификаторы, пути поиска в Windows, больше #[must_use] в стандартной библиотеке