В Rust структура может иметь метод, который берет на себя ответственность за структуру и перемещает ее, что делает незаконным повторное использование структуры, например.
struct Foo {}
impl Foo {
fn consume(self) -> u32 {
println!("consumed!");
42
}
}
fn main() {
let x = Foo {};
let val_1 = x.consume();
let val_2 = x.consume(); // WON'T COMPILE
}
Есть ли способ представить подобное изменение состояния, используя систему типов TypeScript?
Однако этот аспект Rust не уникален. В C++ уже некоторое время можно выражать ту же семантику (хотя и не с помощью методов, поскольку this всегда является указателем).
Это невозможно; его запрашивают в ms/TS#16148 . Самое близкое, что вы можете сделать, это использовать методы утверждения, но им не разрешено ничего возвращать, поэтому, хотя вы можете сделать так, чтобы x.consume(); можно было записать только один раз, let val_1 = x.consume() не присвоил бы val_1 ничего полезного. Это полностью решает вопрос? Если да, то я напишу ответ с объяснением; если нет, то что мне не хватает?
@cdhowie прав, но C++ не может полностью предоставить те же гарантии, что и средство проверки заимствований Rust.
@aled Я не спорю с этим. Я лишь говорю, что идея о том, что функция может принимать значение (о чем этот вопрос), не уникальна для Rust. Эта идея намного предшествует этому.
@cdhowie: Что в C++ позволяет этого добиться? Обратите внимание, что важной частью является не концепция операции, которая делает объект непригодным для использования, а возможность представить это в системе типов или иным образом обеспечить корректность во время компиляции (тоже не новая идея в Rust, но не из-за C++). , насколько я знаю).
Подобные идеи также присутствуют в линейной и аффинной логике, которые были представлены в 1987 году.
@jcalz, да, не стесняйтесь добавлять это в качестве ответа (не нужно ждать одобрения спрашивающего :).
Я обнаружил, что если я не получу какой-то ответ «да, это то, что я ищу», то есть большая вероятность, что я напишу полный ответ, и ОП скажет: «О, это на самом деле не то, о чем я спрашиваю». about», а затем вопрос редактируется, и мой ответ тратит наше время. Мне не нужно ждать какого-то разрешения, но долгий опыт научил меня сначала убедиться, что все на одной волне. Скоро удалю этот комментарий.






TypeScript в настоящее время не поддерживает субструктурные типы , такие как линейные/аффинные типы, которые необходимы для представления той модели владения, которую вы пытаетесь здесь использовать. На него уже давно поступил запрос на открытую функцию по адресу microsoft/TypeScript#16148, который указан как «ожидает дополнительных отзывов», что означает, что им нужно услышать больше запросов сообщества на нее, прежде чем они серьезно рассмотрят возможность ее реализации. И этого, похоже, недостаточно. Вам не мешало бы добавить 👍 и описать свой вариант использования, если он убедителен, но, вероятно, это тоже не поможет.
TypeScript на самом деле не моделирует произвольные изменения состояния в своей системе типов. На самом деле он допускает только сужение , а это означает, что в лучшем случае вы можете сделать что-то с x, что заставит TypeScript рассматривать его как более конкретный тип. Вообще говоря, более конкретные типы имеют больше возможностей, чем менее конкретные, поэтому, на первый взгляд, это не то, что вы можете сделать легко. Вы могли бы использовать функции утверждения , чтобы сузить x.consume до никогда не печатать , что выдаст вам ошибку, если вы ее вызовете. Но у функций утверждения есть оговорки: они не могут возвращать значение (поэтому присвоение val_1 мало что даст), и вам нужна явная аннотация типа. Итак, самое близкое к тому, что вы хотите, я могу получить так:
class Foo {
consume(
ret: { value: number; }
): asserts this is { consume: never } {
console.info("consumed!")
ret.value = 42;
}
}
const foo: Foo = new Foo();
const ret = { value: 0 };
foo.consume(ret);
let val1 = ret.value;
// ^?
console.info(val1); // 42
foo.consume(ret);
// ~~~~~~~
// error! This expression is not callable.
Здесь экземпляр Foo имеет метод утверждения consume(), который принимает параметр ret, который действует как контейнер для желаемого возвращаемого значения. Если вы вызовете consume() на экземпляре Foo (который явно помечен как Foo), то он мутирует ret и сузит экземпляр так, что consume будет иметь тип never и его нельзя будет вызвать снова.
Будет ли это на самом деле лучше, чем просто отказаться и представить состояние каким-либо другим способом (например, просто вызвать consume() несколько раз, но вернуть кешированное значение после первого раза и т. д.), зависит от вашего варианта использования. Я бы сомневался, что методы утверждения - это правильный путь, но на самом деле это выходит за рамки вопроса.
Rust и его система владения/проверка заимствований довольно уникальны, в Typescript нет ничего достаточно близкого. Лучшее, что я могу придумать, это установить переменную и выдать ошибку во время выполнения, но не во время компиляции.