Возврат ссылки на структуру из метода, объявленную в этом самом методе, является одним из самых классических примеров «висячих ссылок». Но что если возвращается не ссылка, а структура, содержащая ссылку? И не явно, а через вызов другого метода? Как понять, где у нас явный «провис ссылки», а где нормальный код? Звучит как какая то «дичь», но подобный кейс — вполне реальная боль для авторов языков программирования.
Давайте посмотрим на примере Rust и C# как авторы решают эту неоднозначную проблему.
Постановка задачи
Как понять в compile time, что ссылка не выходит за scope метода, в случае вызова метода, в который передается ссылка на объект, и возвращается структура, содержащая ссылку как поле, и данная структура выходит за контекст обозначенного метода?
Звучит мудрено, лучше посмотреть по коду:
pub struct MyStruct<'a> { reference: &'a i32, } pub fn test<'a>() -> MyStruct<'a> { let integer = 42; return create(&integer); }
ref struct MyStruct { public MyStruct(ref int reference) { Reference = ref reference; } public ref int Reference; } MyStruct Test() { var integer = 42; return Create(ref integer); }
Проблема данного кода заключается в методе Create. Он может как приводить к «висячей ссылке»:
fn create(reference: &i32) -> MyStruct { return MyStruct { reference }; }
MyStruct Create(ref int reference) { return new MyStruct(ref reference); }
Так и возвращать вполне адекватное значение
fn create(reference: &i32) -> MyStruct { println!("{}", reference); return MyStruct { reference: Box::leak(Box::new(42)) }; }
MyStruct Create(ref int reference) { Console.WriteLine(reference); var arr = new[] {42}; return new MyStruct(ref arr[0]); }
(лайвтаймы для примеров из Rust опущены, чтобы не спойлерить)
И как же компилятору «понять», когда метод Create «хороший», а когда «плохой»? В примерах выше компилятор выдает ошибки при вызове метода Create, что есть false negative, ведь данный метод может быть как «плохим», так и «хорошим».
C# Way
Что делать разработчикам языка, если у них есть мощный компилятор и сообщество, открытое к приключениям? Конечно, вводить новые ключевые слова!
Представляю, scoped!
Копипастить документацию я не буду (детали тут https://learn.microsoft.com/en-us/dotnet/csharp/language-reference/proposals/csharp-11.0/low-level-struct-improvements), лучше скажу в двух словах
Когда вы помечаете ref параметр как scoped, метод будет «знать», что ref будет ссылаться на значение, которое «живет» только в вызывающем методе
Другими словами, когда метод принимает ref, компилятору не сильно важно, откуда она пришла, runtime разберется (вот тут много деталей https://tooslowexception.com/managed-pointers-in-net/).
А вот если указать scoped, то на этапе компиляции будут применятся ограничения, не позволяющие «провиснуть» ссылкам. Например следующий код
MyStruct Create(scoped ref int reference) { return new MyStruct(ref reference); }
Вернет ошибку [CS9075] Cannot return a parameter by reference ‘reference’ because it is scoped to the current method.
И это то что нам нужно — указав для метода Create параметр reference как scoped мы переносим ошибку с вызова метода в его реализацию. При этом «хороший» метод Create компилируется без ошибок:
MyStruct Create(scoped ref int reference) { Console.WriteLine(reference); var arr = new[] {42}; return new MyStruct(ref arr[0]); }
Rust Way
Риторический вопрос: если в документации по C# упоминаются lifetimes, то откуда взята эта фича? Я думаю, Вы поняли, как эта проблема решается в Rust: просто добавь lifetime!
fn create<'a, 'b>(reference: &'b i32) -> MyStruct<'a> { return MyStruct { reference }; }
Обратите внимание, мы объявили два lifetimes: для входа и для выхода. И они не пересекаются! Таким образом функция create «знает», что ссылка reference живет в другом контексте, нежели возвращаемое значение.
При этом «хорошая» версия create отлично компилируется, так как там именно что lifetimes различаются:
fn create<'a, 'b>(reference: &'b i32) -> MyStruct<'a> { println!("{}", reference); return MyStruct { reference: Box::leak(Box::new(42)) }; }
Заключение
Казалось бы, C# и Rust в одном предложении не должны встречаться, но это не так. Модель работы со структурами и ссылками достаточно похожи. Остальные примеры Вы можете посмотреть в видео-подкасте по ссылке ниже:
ссылка на оригинал статьи https://habr.com/ru/post/718424/
Добавить комментарий