Представление потребляемого одноразового объекта в TypeScript

В 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 и его система владения/проверка заимствований довольно уникальны, в Typescript нет ничего достаточно близкого. Лучшее, что я могу придумать, это установить переменную и выдать ошибку во время выполнения, но не во время компиляции.

Alejandro 07.05.2024 20:42

Однако этот аспект Rust не уникален. В C++ уже некоторое время можно выражать ту же семантику (хотя и не с помощью методов, поскольку this всегда является указателем).

cdhowie 07.05.2024 21:05

Это невозможно; его запрашивают в ms/TS#16148 . Самое близкое, что вы можете сделать, это использовать методы утверждения, но им не разрешено ничего возвращать, поэтому, хотя вы можете сделать так, чтобы x.consume(); можно было записать только один раз, let val_1 = x.consume() не присвоил бы val_1 ничего полезного. Это полностью решает вопрос? Если да, то я напишу ответ с объяснением; если нет, то что мне не хватает?

jcalz 07.05.2024 21:12

@cdhowie прав, но C++ не может полностью предоставить те же гарантии, что и средство проверки заимствований Rust.

aled 07.05.2024 21:34

@aled Я не спорю с этим. Я лишь говорю, что идея о том, что функция может принимать значение (о чем этот вопрос), не уникальна для Rust. Эта идея намного предшествует этому.

cdhowie 07.05.2024 21:49

@cdhowie: Что в C++ позволяет этого добиться? Обратите внимание, что важной частью является не концепция операции, которая делает объект непригодным для использования, а возможность представить это в системе типов или иным образом обеспечить корректность во время компиляции (тоже не новая идея в Rust, но не из-за C++). , насколько я знаю).

Ry- 08.05.2024 04:57

Подобные идеи также присутствуют в линейной и аффинной логике, которые были представлены в 1987 году.

jthulhu 08.05.2024 08:06

@jcalz, да, не стесняйтесь добавлять это в качестве ответа (не нужно ждать одобрения спрашивающего :).

kpozin 08.05.2024 18:30

Я обнаружил, что если я не получу какой-то ответ «да, это то, что я ищу», то есть большая вероятность, что я напишу полный ответ, и ОП скажет: «О, это на самом деле не то, о чем я спрашиваю». about», а затем вопрос редактируется, и мой ответ тратит наше время. Мне не нужно ждать какого-то разрешения, но долгий опыт научил меня сначала убедиться, что все на одной волне. Скоро удалю этот комментарий.

jcalz 08.05.2024 20:06
Зод: сила проверки и преобразования данных
Зод: сила проверки и преобразования данных
Сегодня я хочу познакомить вас с библиотекой Zod и раскрыть некоторые ее особенности, например, возможности валидации и трансформации данных, а также...
Как заставить Remix работать с Mantine и Cloudflare Pages/Workers
Как заставить Remix работать с Mantine и Cloudflare Pages/Workers
Мне нравится библиотека Mantine Component , но заставить ее работать без проблем с Remix бывает непросто.
Угловой продивер
Угловой продивер
Оригинал этой статьи на турецком языке. ChatGPT используется только для перевода на английский язык.
TypeScript против JavaScript
TypeScript против JavaScript
TypeScript vs JavaScript - в чем различия и какой из них выбрать?
Синхронизация localStorage в масштабах всего приложения с помощью пользовательского реактивного хука useLocalStorage
Синхронизация localStorage в масштабах всего приложения с помощью пользовательского реактивного хука useLocalStorage
Не все нужно хранить на стороне сервера. Иногда все, что вам нужно, это постоянное хранилище на стороне клиента для хранения уникальных для клиента...
Что такое ленивая загрузка в Angular и как ее применять
Что такое ленивая загрузка в Angular и как ее применять
Ленивая загрузка - это техника, используемая в Angular для повышения производительности приложения путем загрузки модулей только тогда, когда они...
0
9
108
1
Перейти к ответу Данный вопрос помечен как решенный

Ответы 1

Ответ принят как подходящий

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() несколько раз, но вернуть кешированное значение после первого раза и т. д.), зависит от вашего варианта использования. Я бы сомневался, что методы утверждения - это правильный путь, но на самом деле это выходит за рамки вопроса.

Детская площадка, ссылка на код

Другие вопросы по теме