tsclang 0.0.1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/CONCEPT.md +3517 -0
- package/LICENSE +21 -0
- package/README.md +2 -0
- package/bin/index.js +3 -0
- package/package.json +26 -0
package/CONCEPT.md
ADDED
|
@@ -0,0 +1,3517 @@
|
|
|
1
|
+
# TypeScript C (tsc) — Language Design Concept
|
|
2
|
+
|
|
3
|
+
## Overview
|
|
4
|
+
|
|
5
|
+
A TypeScript-inspired language that compiles to C and auto-generates build files (CMakeLists.txt).
|
|
6
|
+
|
|
7
|
+
- File extension: `.tsc`
|
|
8
|
+
- CLI: `tsclang`
|
|
9
|
+
- Output: `.c` / `.h` files + `CMakeLists.txt`
|
|
10
|
+
|
|
11
|
+
---
|
|
12
|
+
|
|
13
|
+
## Установка
|
|
14
|
+
|
|
15
|
+
### Требования
|
|
16
|
+
|
|
17
|
+
- Node.js `>=18.0.0`
|
|
18
|
+
- npm `>=9.0.0`
|
|
19
|
+
- CMake `>=3.16` (для emit: binary / hex)
|
|
20
|
+
- Компилятор C: gcc, clang, или avr-gcc (для AVR targets)
|
|
21
|
+
|
|
22
|
+
### Установка CLI
|
|
23
|
+
|
|
24
|
+
```bash
|
|
25
|
+
# Глобальная установка (рекомендуется)
|
|
26
|
+
npm install -g tsclang
|
|
27
|
+
|
|
28
|
+
# Обновление
|
|
29
|
+
npm update -g tsclang
|
|
30
|
+
|
|
31
|
+
# Проверка установки
|
|
32
|
+
tsclang --version
|
|
33
|
+
|
|
34
|
+
# Запуск без установки
|
|
35
|
+
npx tsclang build
|
|
36
|
+
```
|
|
37
|
+
|
|
38
|
+
---
|
|
39
|
+
|
|
40
|
+
## Core Goals
|
|
41
|
+
|
|
42
|
+
- [ ] TypeScript-like syntax and type system
|
|
43
|
+
- [ ] Compiles to readable, idiomatic C
|
|
44
|
+
- [ ] Auto-generates CMakeLists.txt
|
|
45
|
+
- [ ] Dependency/library management
|
|
46
|
+
|
|
47
|
+
---
|
|
48
|
+
|
|
49
|
+
## Design Decisions
|
|
50
|
+
|
|
51
|
+
### Syntax
|
|
52
|
+
|
|
53
|
+
#### Переменные
|
|
54
|
+
|
|
55
|
+
- `let` — мутабельная переменная: можно переприсвоить, можно вызывать `mut` методы, можно передавать как `Mut<T>`
|
|
56
|
+
- `const` — иммутабельная: нельзя переприсвоить, нельзя вызывать `mut` методы, нельзя передавать как `Mut<T>`
|
|
57
|
+
|
|
58
|
+
#### Перегрузка функций
|
|
59
|
+
|
|
60
|
+
Перегрузка по типам и по количеству параметров. Компилятор выбирает нужную версию на callsite, в C генерирует функции с mangled именами:
|
|
61
|
+
|
|
62
|
+
```typescript
|
|
63
|
+
// по типам
|
|
64
|
+
function process(x: i32): void { ... } // → process_i32 в C
|
|
65
|
+
function process(x: string): void { ... } // → process_string в C
|
|
66
|
+
|
|
67
|
+
process(42); // вызывает process_i32
|
|
68
|
+
process("hello"); // вызывает process_string
|
|
69
|
+
|
|
70
|
+
// по количеству параметров
|
|
71
|
+
function foo(x: i32): void { ... } // → foo_i32 в C
|
|
72
|
+
function foo(x: i32, y: i32): void { ... } // → foo_i32_i32 в C
|
|
73
|
+
|
|
74
|
+
foo(1); // вызывает foo_i32
|
|
75
|
+
foo(1, 2); // вызывает foo_i32_i32
|
|
76
|
+
|
|
77
|
+
// комбинация
|
|
78
|
+
function add(a: i32, b: i32): i32 { ... } // → add_i32_i32
|
|
79
|
+
function add(a: f64, b: f64): f64 { ... } // → add_f64_f64
|
|
80
|
+
function add(a: string, b: string): string { ... } // → add_string_string
|
|
81
|
+
```
|
|
82
|
+
|
|
83
|
+
Перегрузка работает и для методов класса:
|
|
84
|
+
```typescript
|
|
85
|
+
class Printer {
|
|
86
|
+
print(x: i32): void { ... }
|
|
87
|
+
print(x: string): void { ... }
|
|
88
|
+
}
|
|
89
|
+
```
|
|
90
|
+
|
|
91
|
+
#### Ограничение: extern "C" запрещает перегрузку
|
|
92
|
+
|
|
93
|
+
`extern "C"` функции имеют фиксированное C-имя — манглинг невозможен. Перегрузка — ошибка компилятора:
|
|
94
|
+
|
|
95
|
+
```typescript
|
|
96
|
+
// ❌ импорт из C — линковщик не найдёт mangled имена
|
|
97
|
+
extern "C" function SDL_SetWindowSize(w: any, width: i32, height: i32): void { ... }
|
|
98
|
+
extern "C" function SDL_SetWindowSize(w: any, size: i32): void { ... }
|
|
99
|
+
// ошибка: extern "C" функции не могут быть перегружены
|
|
100
|
+
|
|
101
|
+
// ❌ экспорт в C — C-код ищет символ "process", а не "process_string"
|
|
102
|
+
export extern "C" function process(data: string): void { ... }
|
|
103
|
+
export extern "C" function process(data: i32): void { ... }
|
|
104
|
+
// ошибка: extern "C" функции не могут быть перегружены
|
|
105
|
+
|
|
106
|
+
// ✅ правильно — разные имена для C, обёртка с перегрузкой внутри TSC
|
|
107
|
+
extern "C" function SDL_SetWindowSize(w: any, width: i32, height: i32): void { ... }
|
|
108
|
+
|
|
109
|
+
export extern "C" function process_str(data: string): void { ... }
|
|
110
|
+
export extern "C" function process_int(data: i32): void { ... }
|
|
111
|
+
|
|
112
|
+
// внутренняя перегрузка — ok
|
|
113
|
+
function process(data: string): void { process_str(data); }
|
|
114
|
+
function process(data: i32): void { process_int(data); }
|
|
115
|
+
```
|
|
116
|
+
|
|
117
|
+
#### Дефолтные параметры
|
|
118
|
+
|
|
119
|
+
Работают для функций, методов и конструкторов. На callsite компилятор подставляет дефолтное значение:
|
|
120
|
+
|
|
121
|
+
```typescript
|
|
122
|
+
function greet(name: string, greeting: string = "Hello"): string {
|
|
123
|
+
return `${greeting}, ${name}!`;
|
|
124
|
+
}
|
|
125
|
+
|
|
126
|
+
greet("Alice"); // "Hello, Alice!"
|
|
127
|
+
greet("Alice", "Hi"); // "Hi, Alice!"
|
|
128
|
+
|
|
129
|
+
// методы
|
|
130
|
+
class Printer {
|
|
131
|
+
print(text: string, times: i32 = 1): void { ... }
|
|
132
|
+
}
|
|
133
|
+
|
|
134
|
+
printer.print("hi"); // times=1
|
|
135
|
+
printer.print("hi", 3); // times=3
|
|
136
|
+
```
|
|
137
|
+
|
|
138
|
+
- Дефолтные параметры должны быть в конце списка
|
|
139
|
+
- Дефолтное значение — константа или литерал, не выражение с побочными эффектами
|
|
140
|
+
- Запрещено иметь overload, сигнатура которого совпадает с вызовом другого overload при подстановке дефолтных значений — ошибка компилятора:
|
|
141
|
+
|
|
142
|
+
```typescript
|
|
143
|
+
function foo(x: i32, y: i32 = 0): void { ... }
|
|
144
|
+
function foo(x: i32): void { ... }
|
|
145
|
+
// ошибка: ambiguous overload — foo(x: i32) совпадает с foo(x: i32, y: i32 = 0) при y=0
|
|
146
|
+
```
|
|
147
|
+
|
|
148
|
+
#### Функции
|
|
149
|
+
|
|
150
|
+
- Ключевое слово: `function`
|
|
151
|
+
```typescript
|
|
152
|
+
function add(a: i32, b: i32): i32 {
|
|
153
|
+
return a + b;
|
|
154
|
+
}
|
|
155
|
+
```
|
|
156
|
+
- **Стрелочные функции** — сокращённый синтаксис, тип выводится:
|
|
157
|
+
```typescript
|
|
158
|
+
const add = (a: i32, b: i32): i32 => a + b; // expression body
|
|
159
|
+
const add = (a: i32, b: i32): i32 => {
|
|
160
|
+
return a + b;
|
|
161
|
+
}; // block body
|
|
162
|
+
```
|
|
163
|
+
- **Анонимные функции** — `function` без имени, присваивается переменной или передаётся аргументом:
|
|
164
|
+
|
|
165
|
+
```typescript
|
|
166
|
+
const add = function (a: i32, b: i32): i32 {
|
|
167
|
+
return a + b;
|
|
168
|
+
};
|
|
169
|
+
|
|
170
|
+
array.sort(function (a: i32, b: i32): i32 {
|
|
171
|
+
return a - b;
|
|
172
|
+
});
|
|
173
|
+
```
|
|
174
|
+
|
|
175
|
+
- **IIFE** — немедленный вызов функции:
|
|
176
|
+
```typescript
|
|
177
|
+
// стрелочная функция
|
|
178
|
+
((a: i32, b: i32) => a + b)(1, 2); // => 3
|
|
179
|
+
|
|
180
|
+
// блочное тело
|
|
181
|
+
((a: i32, b: i32): i32 => {
|
|
182
|
+
return a + b;
|
|
183
|
+
})(1, 2); // => 3
|
|
184
|
+
|
|
185
|
+
// анонимная функция
|
|
186
|
+
(function (a: i32, b: i32): i32 {
|
|
187
|
+
return a + b;
|
|
188
|
+
})(1, 2); // => 3
|
|
189
|
+
```
|
|
190
|
+
- **Замыкания** — стрелочные функции захватывают переменные из внешнего скопа:
|
|
191
|
+
```typescript
|
|
192
|
+
let multiplier = 3;
|
|
193
|
+
const triple = (x: i32) => x * multiplier; // захватывает multiplier
|
|
194
|
+
```
|
|
195
|
+
- Захват **по значению** для примитивов (копируется в момент создания замыкания); `T | null` где T — примитив, тоже захватывается по значению (copy), несмотря на struct-представление в C:
|
|
196
|
+
```typescript
|
|
197
|
+
let x: i32 | null = 5;
|
|
198
|
+
const fn = () => console.log(x);
|
|
199
|
+
x = null;
|
|
200
|
+
fn(); // 5 — захвачена копия на момент создания
|
|
201
|
+
```
|
|
202
|
+
- Захват **по ссылке** для сложных типов (следует правилам borrow checker) — по умолчанию
|
|
203
|
+
- Явный список захвата — те же типы что везде: `T`, `Ref<T>`, `Mut<T>`, `Shared<T>`:
|
|
204
|
+
```typescript
|
|
205
|
+
const fn = [data: Data]() => process(data); // T — move (Owner)
|
|
206
|
+
const fn = [data: Ref<Data>]() => data.length; // Ref — immutable borrow
|
|
207
|
+
const fn = [data: Mut<Data>]() => { data.push(1); }; // Mut — mutable borrow
|
|
208
|
+
```
|
|
209
|
+
- Список захвата нужен когда компилятор не может вывести тип или нужен move
|
|
210
|
+
- В C компилируется в struct с захваченными переменными + функцию принимающую этот struct
|
|
211
|
+
|
|
212
|
+
#### Семантика передачи значений
|
|
213
|
+
|
|
214
|
+
- **Примитивы** (`i8`..`i64`, `u8`..`u64`, `f32`, `f64`, `boolean`) — всегда **по значению** (copy)
|
|
215
|
+
- **Сложные типы** (объекты, массивы, коллекции, структуры, классы, строки) — управляются ownership системой
|
|
216
|
+
- Явные `&` аннотации не нужны для базовых случаев — компилятор решает сам
|
|
217
|
+
|
|
218
|
+
### Type System
|
|
219
|
+
|
|
220
|
+
#### Generics
|
|
221
|
+
|
|
222
|
+
- **Монорфизация** — компилятор генерирует отдельный код для каждого конкретного типа:
|
|
223
|
+
- `identity<i32>` → `identity_i32` в C
|
|
224
|
+
- `identity<User>` → `identity_User` в C
|
|
225
|
+
- **Синтаксис** — TypeScript-стиль `<T>`:
|
|
226
|
+
```typescript
|
|
227
|
+
function identity<T>(x: T): T { return x; }
|
|
228
|
+
function map<T, U>(arr: Ref<T[]>, f: (x: Ref<T>) => U): U[] { ... }
|
|
229
|
+
|
|
230
|
+
class Stack<T> {
|
|
231
|
+
items: T[];
|
|
232
|
+
mut push(item: T): void { ... }
|
|
233
|
+
mut pop(): T { ... }
|
|
234
|
+
}
|
|
235
|
+
```
|
|
236
|
+
- **Bounds — нет**, проверка при инстанцировании. Правила ownership применяются в момент подстановки конкретного типа:
|
|
237
|
+
```typescript
|
|
238
|
+
first<i32>(arr); // ok — примитив, копируется
|
|
239
|
+
first<User>(arr); // ошибка в точке вызова: User — сложный тип, нельзя вернуть T из Ref<T[]>
|
|
240
|
+
```
|
|
241
|
+
- **Ownership с generics** — `Ref<T>`, `Mut<T>`, `Shared<T>`, `Weak<T>` работают как обычно:
|
|
242
|
+
```typescript
|
|
243
|
+
function first<T>(arr: Ref<T[]>): Ref<T> { ... } // borrow элемента
|
|
244
|
+
function pop<T>(arr: Mut<T[]>): T { ... } // move с удалением
|
|
245
|
+
function process<T>(graph: Shared<T>) { ... } // ARC
|
|
246
|
+
```
|
|
247
|
+
|
|
248
|
+
#### Типизация
|
|
249
|
+
|
|
250
|
+
- **Номинальная** — тип определяется именем, не формой
|
|
251
|
+
- `type Point = { x: f64, y: f64 }` и `type Vector = { x: f64, y: f64 }` — разные типы
|
|
252
|
+
- **Type inference** — тип выводится если не указан явно
|
|
253
|
+
- `const p = { x: 1, y: 0 }` → `{ x: f64, y: f64 }` → анонимная struct в C
|
|
254
|
+
- **Автокаст числовых типов:**
|
|
255
|
+
- Widening **без потерь** — неявно, молча:
|
|
256
|
+
- `i8`/`i16`/`i32` → любой больший int (`i64`)
|
|
257
|
+
- `u8`/`u16`/`u32` → любой больший uint (`u64`)
|
|
258
|
+
- `i32` → `f64`, `u32` → `f64`, `f32` → `f64`
|
|
259
|
+
- Widening **с потерей точности** — требует явный `as`:
|
|
260
|
+
- `i32` → `f32`, `i64` → `f32`, `i64` → `f64`, `u64` → `f64`
|
|
261
|
+
- Narrowing (f64→i32 и т.д.) — всегда требует `as`
|
|
262
|
+
- **Оператор `as`** — явное приведение типа, три случая:
|
|
263
|
+
```typescript
|
|
264
|
+
// 1. Числовые типы — C-cast, может быть lossy
|
|
265
|
+
3.14 as i32 // (int32_t)3.14 в C → 3
|
|
266
|
+
1000 as i8 // переполнение — поведение как в C (implementation-defined)
|
|
267
|
+
|
|
268
|
+
// 2. Non-null assertion — убрать null из типа без проверки
|
|
269
|
+
let x: i32 | null = getValue();
|
|
270
|
+
let y = x as i32; // runtime error если x == null
|
|
271
|
+
// лучше использовать if (x != null) для безопасности
|
|
272
|
+
|
|
273
|
+
// 3. any — явный cast когда тип неизвестен
|
|
274
|
+
let val: any = getFromC();
|
|
275
|
+
let s = val as string;
|
|
276
|
+
```
|
|
277
|
+
- **`as` НЕ работает для:**
|
|
278
|
+
- ownership типов: `user as Ref<User>` — ошибка компилятора
|
|
279
|
+
- конвертации строк: `42 as string` — ошибка, используй `.toString()`
|
|
280
|
+
|
|
281
|
+
- **`as` для struct/interface** — структурная совместимость проверяется компилятором:
|
|
282
|
+
```typescript
|
|
283
|
+
interface Point { x: f64; y: f64; }
|
|
284
|
+
|
|
285
|
+
let p = { x: 1.0, y: 2.0 }; // анонимная struct
|
|
286
|
+
foo(p as Point); // ok — поля совпадают
|
|
287
|
+
|
|
288
|
+
let q = { x: 1.0, z: 2.0 };
|
|
289
|
+
foo(q as Point); // ошибка: поле 'z' не совпадает с 'y'
|
|
290
|
+
|
|
291
|
+
// лучше — явная аннотация сразу:
|
|
292
|
+
let p: Point = { x: 1.0, y: 2.0 };
|
|
293
|
+
foo(p); // ok, без as
|
|
294
|
+
```
|
|
295
|
+
- **Объектные литералы** без типа → анонимная struct, генерируется компилятором
|
|
296
|
+
- **Пустой объектный литерал `{}`** — ошибка компилятора: struct без полей бессмысленна в TSC, память под поля не выделяется динамически:
|
|
297
|
+
```typescript
|
|
298
|
+
let obj = {}; // ошибка: пустой объектный литерал запрещён
|
|
299
|
+
// hint: используй Map<K, V> для динамических ключей
|
|
300
|
+
// или объяви тип: let obj: { field: T } = { ... }
|
|
301
|
+
let obj = {};
|
|
302
|
+
obj.a = 1; // невозможно — struct фиксирована на этапе компиляции
|
|
303
|
+
|
|
304
|
+
// правильно — динамические ключи:
|
|
305
|
+
let obj = new Map<string, i32>();
|
|
306
|
+
obj.set("a", 1);
|
|
307
|
+
|
|
308
|
+
// правильно — фиксированная struct:
|
|
309
|
+
let obj = { a: 1, b: 2 }; // { a: i32, b: i32 } известна компилятору
|
|
310
|
+
obj.a = 5; // ok
|
|
311
|
+
```
|
|
312
|
+
|
|
313
|
+
#### Числовые типы
|
|
314
|
+
|
|
315
|
+
- Полный набор: `i8`, `i16`, `i32`, `i64`, `u8`, `u16`, `u32`, `u64`, `f32`, `f64`
|
|
316
|
+
- Синоним: `number` = `f64` по умолчанию (совместимость с TypeScript-стилем)
|
|
317
|
+
- Переопределяется через `"defaultNumber"` в `tsc.packages.json`
|
|
318
|
+
- На 8-bit таргетах (`"target": "avr"` и др.) — warning если встречается `f64`
|
|
319
|
+
```json
|
|
320
|
+
// tsc.packages.json — AVR
|
|
321
|
+
{ "target": "avr", "mcu": "atmega328p", "defaultNumber": "f32" }
|
|
322
|
+
```
|
|
323
|
+
```typescript
|
|
324
|
+
// Десктоп (defaultNumber = f64)
|
|
325
|
+
const a = 1; // f64
|
|
326
|
+
const b: number = 1; // f64
|
|
327
|
+
const c: f32 = 1; // f32 (явно)
|
|
328
|
+
|
|
329
|
+
// AVR (defaultNumber = f32)
|
|
330
|
+
const a = 1; // f32
|
|
331
|
+
const b: number = 1; // f32
|
|
332
|
+
const c: f32 = 1; // f32 (явно)
|
|
333
|
+
const d: f64 = 1; // f64 + warning: f64 on 8-bit target is inefficient
|
|
334
|
+
```
|
|
335
|
+
- Type inference выводит конкретный тип для всех значений:
|
|
336
|
+
- числа → `number` (= `f64` или переопределённый тип)
|
|
337
|
+
- строки → `string`, булевые → `boolean`, массивы → `number[]` и т.д.
|
|
338
|
+
- явная аннотация переопределяет: `const i: i32 = 1` → `i32`
|
|
339
|
+
- Сообщения об ошибках используют конкретный тип: `expected f64, got i32`
|
|
340
|
+
- Все числа — примитивы, передаются по значению
|
|
341
|
+
|
|
342
|
+
#### Конвертация типов
|
|
343
|
+
|
|
344
|
+
##### Число → строка
|
|
345
|
+
|
|
346
|
+
Три способа:
|
|
347
|
+
|
|
348
|
+
```typescript
|
|
349
|
+
const age: i32 = 30;
|
|
350
|
+
const pi: f64 = 3.14159;
|
|
351
|
+
|
|
352
|
+
// 1. .toString() — явный метод на любом числовом типе
|
|
353
|
+
const s1 = age.toString(); // "30"
|
|
354
|
+
const s2 = pi.toString(); // "3.14159"
|
|
355
|
+
|
|
356
|
+
// 2. Template literal — автоматически
|
|
357
|
+
const s3 = `Age: ${age}`; // "Age: 30"
|
|
358
|
+
const s4 = `Pi = ${pi}`; // "Pi = 3.14159"
|
|
359
|
+
|
|
360
|
+
// 3. Конкатенация со строкой
|
|
361
|
+
const s5 = "Age: " + age; // "Age: 30"
|
|
362
|
+
|
|
363
|
+
// as — НЕ работает для конвертации в строку:
|
|
364
|
+
const bad = age as string; // ошибка компилятора
|
|
365
|
+
```
|
|
366
|
+
|
|
367
|
+
##### Строка → число
|
|
368
|
+
|
|
369
|
+
Явный парсинг — возвращает результат или ошибку:
|
|
370
|
+
|
|
371
|
+
```typescript
|
|
372
|
+
// parse — бросает ParseError если строка не число
|
|
373
|
+
const age = i32.parse("30"); // i32
|
|
374
|
+
const pi = f64.parse("3.14"); // f64
|
|
375
|
+
const bad = i32.parse("abc"); // throws ParseError
|
|
376
|
+
|
|
377
|
+
// tryParse — возвращает T | null, без throws
|
|
378
|
+
const age = i32.tryParse("30"); // 30
|
|
379
|
+
const bad = i32.tryParse("abc"); // null
|
|
380
|
+
|
|
381
|
+
// использование с обработкой ошибок:
|
|
382
|
+
function getAge(raw: string): i32 throws ParseError {
|
|
383
|
+
return i32.parse(raw)?; // propagate ParseError
|
|
384
|
+
}
|
|
385
|
+
|
|
386
|
+
// использование с дефолтом:
|
|
387
|
+
const age = i32.tryParse(raw) ?? 0; // 0 если не распарсилось
|
|
388
|
+
|
|
389
|
+
// as — НЕ работает для парсинга строк:
|
|
390
|
+
const bad = "30" as i32; // ошибка компилятора: используй i32.parse()
|
|
391
|
+
```
|
|
392
|
+
|
|
393
|
+
Доступно для всех числовых типов: `i8.parse`, `i16.parse`, `i32.parse`, `i64.parse`, `u8.parse`, ..., `f32.parse`, `f64.parse`.
|
|
394
|
+
|
|
395
|
+
##### JS-совместимые глобальные функции
|
|
396
|
+
|
|
397
|
+
Синонимы для привычного JS-синтаксиса:
|
|
398
|
+
|
|
399
|
+
```typescript
|
|
400
|
+
// parseFloat(a) — синоним f64.tryParse(a) → f64 | null
|
|
401
|
+
parseFloat("3.14") // 3.14
|
|
402
|
+
parseFloat("abc") // null
|
|
403
|
+
|
|
404
|
+
// parseInt(a) — парсит как f64, затем обрезает дробную часть → i64 | null
|
|
405
|
+
parseInt("3.14") // 3
|
|
406
|
+
parseInt("42") // 42
|
|
407
|
+
parseInt("abc") // null
|
|
408
|
+
parseInt("-7.9") // -7 (truncate, не floor: к нулю)
|
|
409
|
+
|
|
410
|
+
// Number(a) — синоним parseFloat(a) → f64 | null
|
|
411
|
+
Number("3.14") // 3.14
|
|
412
|
+
Number("abc") // null
|
|
413
|
+
|
|
414
|
+
// String(a) — синоним a.toString() → string (всегда успешно)
|
|
415
|
+
String(42) // "42"
|
|
416
|
+
String(3.14) // "3.14"
|
|
417
|
+
String(true) // "true"
|
|
418
|
+
String(null) // "null"
|
|
419
|
+
```
|
|
420
|
+
|
|
421
|
+
Отличия от JS: `parseInt`/`parseFloat`/`Number` возвращают `T | null` вместо `NaN` — в TSC нет `NaN`.
|
|
422
|
+
|
|
423
|
+
#### Строки
|
|
424
|
+
|
|
425
|
+
- Один тип `string` — heap, всегда передаётся по ссылке (сложный тип)
|
|
426
|
+
- Мутабельность через `let`/`const`
|
|
427
|
+
- Кодировка: **UTF-8** внутри
|
|
428
|
+
- Индексация: **по графемным кластерам** (user-perceived characters)
|
|
429
|
+
- `"0❤️abcАБВ"[1]` → `❤️` (два codepoint-а, один кластер)
|
|
430
|
+
- `s[i]` возвращает `string` (не `char` — тип `char` отсутствует)
|
|
431
|
+
- `s[i]` — O(n), строится обход графем
|
|
432
|
+
- Для сегментации графем — **utf8proc** (UAX #29, ~300KB, C-native, поддерживает все Unicode скрипты включая кириллицу, emoji, Devanagari; работает на embedded; locale-aware операции — отдельный пакет)
|
|
433
|
+
- Срезы: `s[1..3]` → подстрока по графемным индексам
|
|
434
|
+
|
|
435
|
+
#### Специальные типы
|
|
436
|
+
|
|
437
|
+
| Тип TSC | Тип C | Описание |
|
|
438
|
+
|---------|-------|----------|
|
|
439
|
+
| `void` | `void` | отсутствие значения — только для возвращаемого типа функции |
|
|
440
|
+
| `any` | `void*` | неизвестный тип — borrow checker не применяется |
|
|
441
|
+
|
|
442
|
+
```typescript
|
|
443
|
+
function log(msg: string): void { ... } // void — нет return value
|
|
444
|
+
|
|
445
|
+
function getFromC(): any { ... } // void* в C
|
|
446
|
+
let val: any = getFromC();
|
|
447
|
+
let s = val as string; // явный cast обязателен
|
|
448
|
+
```
|
|
449
|
+
|
|
450
|
+
- `void` нельзя использовать как тип переменной — только возвращаемый тип
|
|
451
|
+
- `any` = `void*` в C, **неявно nullable** — `void*` может быть `NULL`; писать `any | null` избыточно и запрещено (ошибка компилятора)
|
|
452
|
+
- `any` отключает borrow checker — **управление памятью ручное**, утечки на совести разработчика; использовать только на границах C interop
|
|
453
|
+
|
|
454
|
+
```typescript
|
|
455
|
+
// void + throws — Result без value-поля в C
|
|
456
|
+
function connect(): void throws IOError { ... }
|
|
457
|
+
// → typedef struct { bool ok; IOError error; } _Result_void_IOError;
|
|
458
|
+
|
|
459
|
+
connect()?; // ok — propagate
|
|
460
|
+
connect()!; // ok — panic on error
|
|
461
|
+
```
|
|
462
|
+
|
|
463
|
+
#### Null
|
|
464
|
+
|
|
465
|
+
- `null` — единственное "отсутствующее значение"
|
|
466
|
+
- `undefined` **отсутствует** — в отличие от JS, нет разделения на `null` и `undefined`
|
|
467
|
+
- `NaN` **отсутствует** — функции парсинга возвращают `T | null` вместо `NaN`; деление на ноль для целых → runtime panic, для float → поведение как в C (`Infinity`, `-Infinity` через IEEE 754, но не `NaN` как значение типа)
|
|
468
|
+
|
|
469
|
+
#### Операторы
|
|
470
|
+
|
|
471
|
+
##### Арифметические
|
|
472
|
+
|
|
473
|
+
| Оператор | Описание |
|
|
474
|
+
|----------|----------|
|
|
475
|
+
| `+` | сложение; для `string` — конкатенация |
|
|
476
|
+
| `-` | вычитание |
|
|
477
|
+
| `*` | умножение |
|
|
478
|
+
| `/` | деление |
|
|
479
|
+
| `%` | остаток от деления |
|
|
480
|
+
| `**` | возведение в степень |
|
|
481
|
+
| `++` | инкремент (prefix / postfix) |
|
|
482
|
+
| `--` | декремент (prefix / postfix) |
|
|
483
|
+
|
|
484
|
+
##### Присваивание
|
|
485
|
+
|
|
486
|
+
| Оператор | Эквивалент |
|
|
487
|
+
|----------|------------|
|
|
488
|
+
| `=` | присваивание |
|
|
489
|
+
| `+=` | `a = a + b` |
|
|
490
|
+
| `-=` | `a = a - b` |
|
|
491
|
+
| `*=` | `a = a * b` |
|
|
492
|
+
| `/=` | `a = a / b` |
|
|
493
|
+
| `%=` | `a = a % b` |
|
|
494
|
+
| `**=` | `a = a ** b` |
|
|
495
|
+
| `&=` | `a = a & b` |
|
|
496
|
+
| `\|=` | `a = a \| b` |
|
|
497
|
+
| `^=` | `a = a ^ b` |
|
|
498
|
+
| `<<=` | `a = a << b` |
|
|
499
|
+
| `>>=` | `a = a >> b` |
|
|
500
|
+
| `>>>=` | `a = a >>> b` |
|
|
501
|
+
| `&&=` | `a = a && b` |
|
|
502
|
+
| `\|\|=` | `a = a \|\| b` |
|
|
503
|
+
| `??=` | `a = a ?? b` |
|
|
504
|
+
|
|
505
|
+
##### Сравнения
|
|
506
|
+
|
|
507
|
+
| Оператор | Описание |
|
|
508
|
+
|----------|----------|
|
|
509
|
+
| `==` | равенство (в TSC нет type coercion — идентично `===`) |
|
|
510
|
+
| `!=` | неравенство (идентично `!==`) |
|
|
511
|
+
| `===` | строгое равенство |
|
|
512
|
+
| `!==` | строгое неравенство |
|
|
513
|
+
| `<` | меньше |
|
|
514
|
+
| `>` | больше |
|
|
515
|
+
| `<=` | меньше или равно |
|
|
516
|
+
| `>=` | больше или равно |
|
|
517
|
+
|
|
518
|
+
> В TSC нет неявного приведения типов, поэтому `==` и `===` ведут себя одинаково. Рекомендуется `===` для ясности.
|
|
519
|
+
|
|
520
|
+
##### Логические
|
|
521
|
+
|
|
522
|
+
| Оператор | Описание |
|
|
523
|
+
|----------|----------|
|
|
524
|
+
| `&&` | возвращает первый falsy операнд, или последний если все truthy |
|
|
525
|
+
| `\|\|` | возвращает первый truthy операнд, или последний если все falsy |
|
|
526
|
+
| `!` | логическое НЕ, возвращает `boolean` |
|
|
527
|
+
| `??` | возвращает правый операнд если левый `null` (не реагирует на `0`, `""`, `false`) |
|
|
528
|
+
|
|
529
|
+
Поведение `||` и `&&` аналогично JS — возвращают **сам операнд**, не `boolean`:
|
|
530
|
+
|
|
531
|
+
```typescript
|
|
532
|
+
const name = user.name || "Anonymous" // string — "Anonymous" если name == ""
|
|
533
|
+
const port = config.port || 8080 // i32 — 8080 если port == 0
|
|
534
|
+
const value = a && b && c // тип c — c если все truthy, иначе первый falsy
|
|
535
|
+
|
|
536
|
+
// ?? vs || — разница:
|
|
537
|
+
const x = value ?? "default" // "default" только если value == null
|
|
538
|
+
const y = value || "default" // "default" если value == null, "", 0, false
|
|
539
|
+
```
|
|
540
|
+
|
|
541
|
+
Тип результата `||` и `&&` — компилятор выводит из операндов (должны быть совместимых типов).
|
|
542
|
+
|
|
543
|
+
##### Битовые
|
|
544
|
+
|
|
545
|
+
| Оператор | Описание |
|
|
546
|
+
|----------|----------|
|
|
547
|
+
| `&` | побитовое И |
|
|
548
|
+
| `\|` | побитовое ИЛИ |
|
|
549
|
+
| `^` | побитовое XOR |
|
|
550
|
+
| `~` | побитовое НЕ |
|
|
551
|
+
| `<<` | сдвиг влево |
|
|
552
|
+
| `>>` | сдвиг вправо (знаковый) |
|
|
553
|
+
| `>>>` | сдвиг вправо (беззнаковый) |
|
|
554
|
+
|
|
555
|
+
##### Прочие
|
|
556
|
+
|
|
557
|
+
| Оператор | Описание |
|
|
558
|
+
|----------|----------|
|
|
559
|
+
| `? :` | тернарный оператор |
|
|
560
|
+
| `?.` | optional chaining — обращение к полю/методу если не `null` |
|
|
561
|
+
| `...` | spread |
|
|
562
|
+
|
|
563
|
+
##### Приоритет операторов
|
|
564
|
+
|
|
565
|
+
От высшего к низшему. Операторы на одном уровне — левоассоциативны, если не указано иное.
|
|
566
|
+
|
|
567
|
+
| Приоритет | Оператор(ы) | Ассоциативность |
|
|
568
|
+
|-----------|-------------|-----------------|
|
|
569
|
+
| 18 | `()` группировка | — |
|
|
570
|
+
| 17 | `.` `?.` `[]` вызов `()` | левая |
|
|
571
|
+
| 16 | `++` `--` (postfix) | — |
|
|
572
|
+
| 15 | `!` `~` `+` `-` (unary) `++` `--` (prefix) | правая |
|
|
573
|
+
| 14 | `**` | правая |
|
|
574
|
+
| 13 | `*` `/` `%` | левая |
|
|
575
|
+
| 12 | `+` `-` | левая |
|
|
576
|
+
| 11 | `<<` `>>` `>>>` | левая |
|
|
577
|
+
| 10 | `<` `<=` `>` `>=` | левая |
|
|
578
|
+
| 9 | `==` `!=` `===` `!==` | левая |
|
|
579
|
+
| 8 | `&` | левая |
|
|
580
|
+
| 7 | `^` | левая |
|
|
581
|
+
| 6 | `\|` | левая |
|
|
582
|
+
| 5 | `&&` | левая |
|
|
583
|
+
| 4 | `\|\|` `??` | левая |
|
|
584
|
+
| 3 | `? :` | правая |
|
|
585
|
+
| 2 | `=` `+=` `-=` `*=` `/=` `%=` `**=` `&=` `\|=` `^=` `<<=` `>>=` `>>>=` `&&=` `\|\|=` `??=` | правая |
|
|
586
|
+
|
|
587
|
+
> `??` нельзя смешивать с `||` или `&&` без явных скобок — ошибка компилятора:
|
|
588
|
+
> ```typescript
|
|
589
|
+
> a || b ?? c // error: смешивание || и ?? требует скобок
|
|
590
|
+
> (a || b) ?? c // ok
|
|
591
|
+
> a || (b ?? c) // ok
|
|
592
|
+
> ```
|
|
593
|
+
|
|
594
|
+
#### Truthy / Falsy
|
|
595
|
+
|
|
596
|
+
Как в JS, без `undefined` и `NaN`:
|
|
597
|
+
|
|
598
|
+
| Тип | Falsy | Truthy |
|
|
599
|
+
|-----|-------|--------|
|
|
600
|
+
| `boolean` | `false` | `true` |
|
|
601
|
+
| числовые (`i8`..`f64`) | `0` | любое ненулевое |
|
|
602
|
+
| `string` | `""` (пустая строка) | любая непустая |
|
|
603
|
+
| `T | null` (сложный тип) | `null` | не null |
|
|
604
|
+
| `T | null` (примитив) | `null` или falsy значение | не null и truthy |
|
|
605
|
+
| class / struct | никогда (всегда truthy) | всегда |
|
|
606
|
+
| array / Set / Map | никогда (всегда truthy, даже пустые) | всегда |
|
|
607
|
+
|
|
608
|
+
```typescript
|
|
609
|
+
if ("") { } // falsy
|
|
610
|
+
if ("hi") { } // truthy
|
|
611
|
+
if (0) { } // falsy
|
|
612
|
+
if (42) { } // truthy
|
|
613
|
+
if (null) { } // falsy
|
|
614
|
+
|
|
615
|
+
// string | null — truthy если не null И не ""
|
|
616
|
+
let s: string | null = getValue();
|
|
617
|
+
if (s) {
|
|
618
|
+
// s: string (не null и не пустая)
|
|
619
|
+
}
|
|
620
|
+
|
|
621
|
+
// i32 | null — truthy если не null И не 0
|
|
622
|
+
let n: i32 | null = getValue();
|
|
623
|
+
if (n) {
|
|
624
|
+
// n: i32 (не null и не 0)
|
|
625
|
+
}
|
|
626
|
+
|
|
627
|
+
// class — всегда truthy (non-null по определению)
|
|
628
|
+
let u = new User("Alice");
|
|
629
|
+
if (u) { } // всегда truthy — компилятор выдаёт warning: условие всегда true
|
|
630
|
+
|
|
631
|
+
// array / Set / Map — всегда truthy, даже пустые
|
|
632
|
+
let arr: i32[] = [];
|
|
633
|
+
if (arr) { } // truthy — warning: условие всегда true
|
|
634
|
+
// для проверки на пустоту используй arr.length === 0
|
|
635
|
+
|
|
636
|
+
let m = new Map<string, i32>();
|
|
637
|
+
if (m) { } // truthy — warning: условие всегда true
|
|
638
|
+
// для проверки на пустоту используй m.size === 0
|
|
639
|
+
```
|
|
640
|
+
|
|
641
|
+
Narrowing через truthy/falsy:
|
|
642
|
+
```typescript
|
|
643
|
+
let s: string | null = getValue();
|
|
644
|
+
if (s) {
|
|
645
|
+
console.log(s.length); // s: string — не null, не ""
|
|
646
|
+
} else {
|
|
647
|
+
// s: string | null, но точно null или ""
|
|
648
|
+
}
|
|
649
|
+
```
|
|
650
|
+
|
|
651
|
+
C-output для truthy check:
|
|
652
|
+
```c
|
|
653
|
+
// string | null
|
|
654
|
+
if (s != NULL && s->length > 0) { ... }
|
|
655
|
+
|
|
656
|
+
// i32 | null (struct)
|
|
657
|
+
if (x.has_value && x.value != 0) { ... }
|
|
658
|
+
|
|
659
|
+
// string (non-nullable)
|
|
660
|
+
if (s->length > 0) { ... }
|
|
661
|
+
```
|
|
662
|
+
|
|
663
|
+
- Синтаксис nullable типа: `T | null` — для любых типов, компилятор выбирает реализацию:
|
|
664
|
+
- Сложные типы (строки, массивы, объекты, Map, Set) → `T* = NULL` в C (бесплатно)
|
|
665
|
+
- Примитивы (`i8`..`i64`, `u8`..`u64`, `f32`, `f64`, `boolean`) → `struct { bool has_value; T value; }` в C
|
|
666
|
+
- Компилятор сужает тип после проверки (type narrowing):
|
|
667
|
+
```typescript
|
|
668
|
+
function findIndex(arr: i32[], val: i32): i32 | null {
|
|
669
|
+
for (let i = 0; i < arr.length; i++) {
|
|
670
|
+
if (arr[i] == val) return i;
|
|
671
|
+
}
|
|
672
|
+
return null;
|
|
673
|
+
}
|
|
674
|
+
|
|
675
|
+
const idx = findIndex(arr, 42);
|
|
676
|
+
if (idx != null) {
|
|
677
|
+
// здесь idx — просто i32
|
|
678
|
+
}
|
|
679
|
+
```
|
|
680
|
+
- **Синтаксис `?`** — сахар для `T | null`, работает везде:
|
|
681
|
+
```typescript
|
|
682
|
+
// переменные
|
|
683
|
+
let x?: i32; // то же что let x: i32 | null = null;
|
|
684
|
+
let s?: string; // то же что let s: string | null = null;
|
|
685
|
+
|
|
686
|
+
// параметры функции
|
|
687
|
+
function foo(x: i32, y?: i32) { ... } // y: i32 | null
|
|
688
|
+
foo(1); // y = null
|
|
689
|
+
foo(1, 5); // y = 5
|
|
690
|
+
|
|
691
|
+
// поля класса/структуры
|
|
692
|
+
class User {
|
|
693
|
+
name: string;
|
|
694
|
+
age?: i32; // то же что age: i32 | null
|
|
695
|
+
}
|
|
696
|
+
```
|
|
697
|
+
|
|
698
|
+
- **Optional chaining `?.`** — обращение к полю/методу только если значение не null; возвращает `T | null`:
|
|
699
|
+
```typescript
|
|
700
|
+
const name = user?.profile?.name; // null если user или profile = null
|
|
701
|
+
const len = user?.tags?.length; // i32 | null
|
|
702
|
+
|
|
703
|
+
// методы
|
|
704
|
+
const upper = user?.getName()?.toUpperCase();
|
|
705
|
+
|
|
706
|
+
// C-output (вложенные тернарные операторы или if-цепочки)
|
|
707
|
+
// String* name = (user != NULL && user->profile != NULL) ? user->profile->name : NULL;
|
|
708
|
+
```
|
|
709
|
+
Тип результата `?.` всегда nullable: `T | null`.
|
|
710
|
+
|
|
711
|
+
- **Nullish coalescing `??`** — дефолтное значение если `null`:
|
|
712
|
+
```typescript
|
|
713
|
+
const name = user.name ?? "Anonymous"; // string
|
|
714
|
+
const age = user.age ?? 0; // i32
|
|
715
|
+
|
|
716
|
+
// цепочка с ?.
|
|
717
|
+
const city = user?.address?.city ?? "Unknown";
|
|
718
|
+
```
|
|
719
|
+
Правая часть `??` должна быть того же типа что `T` в `T | null` — ошибка компилятора иначе.
|
|
720
|
+
|
|
721
|
+
C-output зависит от типа:
|
|
722
|
+
```typescript
|
|
723
|
+
// Примитив (struct { bool has_value; T value; }):
|
|
724
|
+
const x: i32 | null = getSomething();
|
|
725
|
+
const y = x ?? 0;
|
|
726
|
+
// → int32_t y = x.has_value ? x.value : 0;
|
|
727
|
+
|
|
728
|
+
// Сложный тип (указатель):
|
|
729
|
+
let s: string | null = getString();
|
|
730
|
+
const result = s ?? "default";
|
|
731
|
+
// → String result = s != NULL ? *s : str("default");
|
|
732
|
+
// s помечается компилятором как перемещённый (moved)
|
|
733
|
+
```
|
|
734
|
+
|
|
735
|
+
#### Индексация и срезы (массивы и строки)
|
|
736
|
+
|
|
737
|
+
Единый синтаксис для массивов и строк. Конец среза всегда **эксклюзивный**, отрицательные индексы считают с конца:
|
|
738
|
+
|
|
739
|
+
| Синтаксис | Результат |
|
|
740
|
+
|-----------|-----------|
|
|
741
|
+
| `arr[i]` | элемент по индексу (отрицательный: с конца) |
|
|
742
|
+
| `arr[1..3]` | элементы 1, 2 |
|
|
743
|
+
| `arr[1..]` | с 1 до конца |
|
|
744
|
+
| `arr[..3]` | с начала до 3 (не включая) |
|
|
745
|
+
| `arr[..]` | весь массив |
|
|
746
|
+
| `arr[-1]` | последний элемент |
|
|
747
|
+
| `arr[0..-1]` | всё кроме последнего |
|
|
748
|
+
| `arr[-2..]` | последние два элемента |
|
|
749
|
+
|
|
750
|
+
#### Date
|
|
751
|
+
|
|
752
|
+
JS-совместимый тип даты/времени. Реализован поверх C `time_t` / `struct tm` из `<time.h>`.
|
|
753
|
+
|
|
754
|
+
Внутреннее представление — `int64_t` (миллисекунды с Unix epoch), как в JS.
|
|
755
|
+
|
|
756
|
+
##### Создание
|
|
757
|
+
|
|
758
|
+
```typescript
|
|
759
|
+
new Date() // текущее время
|
|
760
|
+
new Date(1710936000000) // из миллисекунд с epoch
|
|
761
|
+
new Date("2024-03-20") // из ISO строки
|
|
762
|
+
new Date("2024-03-20T14:30:00.000Z") // ISO с временем
|
|
763
|
+
new Date(2024, 2, 20) // год, месяц (0-11!), день
|
|
764
|
+
new Date(2024, 2, 20, 14, 30, 0, 0) // + часы, минуты, секунды, мс
|
|
765
|
+
```
|
|
766
|
+
|
|
767
|
+
##### Статические методы
|
|
768
|
+
|
|
769
|
+
```typescript
|
|
770
|
+
Date.now() // i64 — текущее время в мс с epoch
|
|
771
|
+
```
|
|
772
|
+
|
|
773
|
+
##### Геттеры
|
|
774
|
+
|
|
775
|
+
```typescript
|
|
776
|
+
const d = new Date("2024-03-20T14:30:00.000Z");
|
|
777
|
+
|
|
778
|
+
d.getFullYear() // i32 — 2024
|
|
779
|
+
d.getMonth() // i32 — 2 (0-11, март = 2)
|
|
780
|
+
d.getDate() // i32 — 20 (день месяца, 1-31)
|
|
781
|
+
d.getDay() // i32 — 3 (день недели, 0=воскресенье)
|
|
782
|
+
d.getHours() // i32 — 14
|
|
783
|
+
d.getMinutes() // i32 — 30
|
|
784
|
+
d.getSeconds() // i32 — 0
|
|
785
|
+
d.getMilliseconds() // i32 — 0
|
|
786
|
+
d.getTime() // i64 — мс с epoch
|
|
787
|
+
d.getTimezoneOffset() // i32 — смещение timezone в минутах
|
|
788
|
+
```
|
|
789
|
+
|
|
790
|
+
##### Сеттеры
|
|
791
|
+
|
|
792
|
+
```typescript
|
|
793
|
+
d.setFullYear(2025)
|
|
794
|
+
d.setMonth(0) // январь
|
|
795
|
+
d.setDate(1)
|
|
796
|
+
d.setHours(12)
|
|
797
|
+
d.setMinutes(0)
|
|
798
|
+
d.setSeconds(0)
|
|
799
|
+
d.setMilliseconds(0)
|
|
800
|
+
d.setTime(1710936000000)
|
|
801
|
+
```
|
|
802
|
+
|
|
803
|
+
##### Форматирование
|
|
804
|
+
|
|
805
|
+
```typescript
|
|
806
|
+
d.toISOString() // "2024-03-20T14:30:00.000Z"
|
|
807
|
+
d.toString() // "Wed Mar 20 2024 14:30:00 GMT+0000"
|
|
808
|
+
d.toDateString() // "Wed Mar 20 2024"
|
|
809
|
+
d.toTimeString() // "14:30:00 GMT+0000"
|
|
810
|
+
d.toLocaleDateString() // локализованная дата
|
|
811
|
+
d.toLocaleTimeString() // локализованное время
|
|
812
|
+
d.toLocaleString() // локализованные дата и время
|
|
813
|
+
d.valueOf() // i64 — то же что getTime()
|
|
814
|
+
```
|
|
815
|
+
|
|
816
|
+
##### C-output
|
|
817
|
+
|
|
818
|
+
```c
|
|
819
|
+
typedef struct { int64_t ms; } Date;
|
|
820
|
+
|
|
821
|
+
// Date.now()
|
|
822
|
+
Date Date_now() {
|
|
823
|
+
struct timespec ts;
|
|
824
|
+
clock_gettime(CLOCK_REALTIME, &ts);
|
|
825
|
+
return (Date){ ts.tv_sec * 1000LL + ts.tv_nsec / 1000000LL };
|
|
826
|
+
}
|
|
827
|
+
|
|
828
|
+
// getFullYear()
|
|
829
|
+
int32_t Date_getFullYear(Date d) {
|
|
830
|
+
time_t t = d.ms / 1000;
|
|
831
|
+
struct tm* tm = gmtime(&t);
|
|
832
|
+
return tm->tm_year + 1900;
|
|
833
|
+
}
|
|
834
|
+
```
|
|
835
|
+
|
|
836
|
+
> На embedded `gmtime` / `localtime` могут быть недоступны — используй `PlainDateTime` (Temporal, в разработке).
|
|
837
|
+
|
|
838
|
+
#### Массивы и коллекции
|
|
839
|
+
|
|
840
|
+
##### Массивы
|
|
841
|
+
|
|
842
|
+
| Синтаксис | Тип | Память |
|
|
843
|
+
|-----------|-----|--------|
|
|
844
|
+
| `[1, 2, 3]` | литерал, динамический | heap |
|
|
845
|
+
| `i32[]` | тип динамического массива | heap |
|
|
846
|
+
| `i32[3]` | фиксированный, ровно 3 элемента | стек |
|
|
847
|
+
|
|
848
|
+
```typescript
|
|
849
|
+
let a = [1, 2, 3]; // динамический, из литерала
|
|
850
|
+
let b: i32[] = []; // пустой динамический
|
|
851
|
+
let c: i32[3] = [1, 2, 3]; // фиксированный, ровно 3 элемента
|
|
852
|
+
let d: i32[] = new Array(100); // capacity=100, length=0 (тип из аннотации)
|
|
853
|
+
let e = new Array<i32>(100); // то же самое, без аннотации
|
|
854
|
+
// ВАЖНО: аргумент new Array(N) — это capacity, не length (расхождение с JS)
|
|
855
|
+
```
|
|
856
|
+
|
|
857
|
+
Фиксированный массив `T[N]`:
|
|
858
|
+
- Размер известен на этапе компиляции, память на стеке
|
|
859
|
+
- Литерал инициализации должен содержать ровно N элементов — иначе ошибка компилятора
|
|
860
|
+
- `push`/`pop` недоступны — ошибка компилятора
|
|
861
|
+
- Передаётся в функции как `Ref<T[]>` / `Mut<T[]>` — фиксированный является подтипом динамического:
|
|
862
|
+
```typescript
|
|
863
|
+
function sum(arr: Ref<i32[]>): i32 { ... } // принимает любой i32 массив
|
|
864
|
+
|
|
865
|
+
let fixed: i32[3] = [1, 2, 3];
|
|
866
|
+
let dynamic: i32[] = [1, 2, 3, 4];
|
|
867
|
+
|
|
868
|
+
sum(fixed); // ok — автоматически как Ref<i32[]>
|
|
869
|
+
sum(dynamic); // ok
|
|
870
|
+
```
|
|
871
|
+
|
|
872
|
+
Методы и свойства динамического массива:
|
|
873
|
+
- `arr.push(item)` — move item в конец массива; бросает при OOM
|
|
874
|
+
```typescript
|
|
875
|
+
let arr: User[] = [];
|
|
876
|
+
let user = new User();
|
|
877
|
+
arr.push(user); // move — arr владеет user
|
|
878
|
+
console.log(user); // ошибка: user перемещён
|
|
879
|
+
```
|
|
880
|
+
- `arr.pop()` — удалить и вернуть последний элемент как owned `T | null`; null если массив пустой
|
|
881
|
+
```typescript
|
|
882
|
+
let last = arr.pop(); // User | null
|
|
883
|
+
if (last != null) {
|
|
884
|
+
last.doSomething(); // ok — last владеет объектом
|
|
885
|
+
}
|
|
886
|
+
// или короче:
|
|
887
|
+
arr.pop()?.doSomething(); // ?. — только если не null
|
|
888
|
+
const u = arr.pop() ?? defaultUser; // ?? — дефолт если null
|
|
889
|
+
```
|
|
890
|
+
- `arr.remove(i)` — удалить по индексу с возвратом ownership
|
|
891
|
+
- `arr.fill(value)` — **TSC**: заполнить все слоты 0..capacity, length становится равным capacity
|
|
892
|
+
- `arr.fill(value, start, end)` — **JS-совместимо**: заполнить индексы `start..end-1` в пределах `0..length`, length не меняется:
|
|
893
|
+
- `end > length` — ошибка компилятора (константы) или runtime error (переменные)
|
|
894
|
+
```typescript
|
|
895
|
+
let arr: i32[] = new Array(100); // capacity=100, length=0
|
|
896
|
+
arr.fill(0); // capacity=100, length=100, все слоты = 0
|
|
897
|
+
arr.fill(5, 0, 10); // индексы 0..9 = 5, length остаётся 100
|
|
898
|
+
arr.fill(5, 90, 110); // ошибка: end=110 > length=100
|
|
899
|
+
```
|
|
900
|
+
- `arr.resize(n)` — уменьшить length до n; если n > length — ошибка компилятора (используй `resize(n, value)`)
|
|
901
|
+
- `arr.resize(n, value)` — изменить length до n, новые слоты заполняются `value`; если n > capacity — автоматически реаллоцирует; при уменьшении `value` игнорируется
|
|
902
|
+
```typescript
|
|
903
|
+
arr.resize(10); // ok — уменьшить, value не нужен
|
|
904
|
+
arr.resize(50); // ошибка компилятора: n > length, используй resize(n, value)
|
|
905
|
+
arr.resize(200, 0); // ok — увеличить, новые слоты = 0, реаллоцирует если нужно
|
|
906
|
+
arr.resize(5, 0); // ok — уменьшить, value игнорируется
|
|
907
|
+
```
|
|
908
|
+
- `arr.length` — количество элементов (доступны индексы `0..length-1`), readonly;
|
|
909
|
+
присвоение `arr.length = n` — ошибка компилятора с подсказкой: `use arr.resize(n) instead`
|
|
910
|
+
```typescript
|
|
911
|
+
let arr: i32[] = new Array(100); // capacity=100, length=0
|
|
912
|
+
arr.push(1);
|
|
913
|
+
arr.push(2); // capacity=100, length=2
|
|
914
|
+
|
|
915
|
+
arr[0]; // ok → 1
|
|
916
|
+
arr[1]; // ok → 2
|
|
917
|
+
arr[2]; // runtime error: index 2 out of bounds (length=2)
|
|
918
|
+
arr[99]; // runtime error: index 99 out of bounds (length=2)
|
|
919
|
+
arr[-1]; // ok → 2 (последний элемент)
|
|
920
|
+
arr[-3]; // runtime error: index -3 out of bounds (length=2)
|
|
921
|
+
|
|
922
|
+
arr.length = 10; // ошибка компилятора: use arr.resize(10) instead
|
|
923
|
+
```
|
|
924
|
+
- `arr.capacity` — заранее выделенная память, читать и записывать:
|
|
925
|
+
```typescript
|
|
926
|
+
let arr: i32[] = new Array(100); // capacity=100, length=0
|
|
927
|
+
arr.fill(0); // capacity=100, length=100
|
|
928
|
+
|
|
929
|
+
// увеличить capacity — length не меняется
|
|
930
|
+
arr.capacity = 200; // capacity=200, length=100
|
|
931
|
+
console.log(arr.capacity); // 200
|
|
932
|
+
console.log(arr.length); // 100
|
|
933
|
+
|
|
934
|
+
// уменьшить capacity ниже length — length обрезается
|
|
935
|
+
arr.capacity = 50; // capacity=50, length=50 (было length=100, обрезано)
|
|
936
|
+
console.log(arr.capacity); // 50
|
|
937
|
+
console.log(arr.length); // 50
|
|
938
|
+
|
|
939
|
+
arr.capacity = 30; // capacity=30, length=30 (было length=50, обрезано)
|
|
940
|
+
console.log(arr.capacity); // 30
|
|
941
|
+
console.log(arr.length); // 30
|
|
942
|
+
```
|
|
943
|
+
|
|
944
|
+
##### Структуры данных под капотом
|
|
945
|
+
|
|
946
|
+
| Тип | Реализация в C | Ключи |
|
|
947
|
+
|-----|----------------|-------|
|
|
948
|
+
| `{}` объектный литерал | `struct` | известны на этапе компиляции |
|
|
949
|
+
| `Map<K, V>` | хеш-таблица | известны только в runtime |
|
|
950
|
+
| `Set<T>` | хеш-множество | известны только в runtime |
|
|
951
|
+
|
|
952
|
+
`Object.keys(obj)` — компилятор знает ключи статически и генерирует их как массив констант. В отличие от JS, `{}` в TSC **не является** хеш-таблицей.
|
|
953
|
+
|
|
954
|
+
##### Map
|
|
955
|
+
|
|
956
|
+
Инициализация:
|
|
957
|
+
```typescript
|
|
958
|
+
// Универсальный — любой тип ключа
|
|
959
|
+
let m = new Map<string, i32>([["a", 1], ["b", 2]]);
|
|
960
|
+
|
|
961
|
+
// Объектный литерал — только string ключи
|
|
962
|
+
let m: Map<string, i32> = { "a": 1, "b": 2 };
|
|
963
|
+
|
|
964
|
+
// Пустая Map
|
|
965
|
+
let m = new Map<string, i32>();
|
|
966
|
+
```
|
|
967
|
+
|
|
968
|
+
Методы:
|
|
969
|
+
```typescript
|
|
970
|
+
m.set(key, value) // key: move (сложный тип) / copy (примитив); value: move — Map владеет обоими
|
|
971
|
+
m.get(key) // key: Ref<K>, возвращает Ref<V> | null (не V | undefined как в JS)
|
|
972
|
+
m.has(key) // key: Ref<K>, boolean
|
|
973
|
+
m.delete(key) // key: Ref<K>, возвращает V | null (owned) — элемент удалён из Map
|
|
974
|
+
m.clear() // void
|
|
975
|
+
m.size // number, readonly
|
|
976
|
+
|
|
977
|
+
// ?. и ?? с Map
|
|
978
|
+
const len = m.get("key")?.length ?? 0; // Ref<string> | null → i32
|
|
979
|
+
const val = m.delete("key") ?? fallback; // V | null → V
|
|
980
|
+
```
|
|
981
|
+
|
|
982
|
+
Примеры ownership:
|
|
983
|
+
```typescript
|
|
984
|
+
let m = new Map<string, User>();
|
|
985
|
+
let user = new User();
|
|
986
|
+
m.set("alice", user); // "alice" — литерал, копируется; user — move
|
|
987
|
+
console.log(user); // ошибка: user перемещён
|
|
988
|
+
|
|
989
|
+
let key = "alice";
|
|
990
|
+
m.set(key, user2); // key — move
|
|
991
|
+
console.log(key); // ошибка: key перемещён
|
|
992
|
+
|
|
993
|
+
let u = m.get("alice"); // Ref<User> | null — borrow из Map
|
|
994
|
+
let u = m.delete("alice"); // User | null — owned, элемент удалён
|
|
995
|
+
|
|
996
|
+
// примитивы — всегда copy
|
|
997
|
+
let m = new Map<string, i32>();
|
|
998
|
+
m.set("x", 42); // 42 скопирован
|
|
999
|
+
m.get("x"); // i32 | null — copy (примитив)
|
|
1000
|
+
```
|
|
1001
|
+
|
|
1002
|
+
Итерация — `k: Ref<K>`, `v: Ref<V>` для сложных типов, copy для примитивов:
|
|
1003
|
+
```typescript
|
|
1004
|
+
for (const [k, v] of m) {
|
|
1005
|
+
v.doSomething(); // ok — immutable метод
|
|
1006
|
+
v.mutMethod(); // ошибка — v это Ref
|
|
1007
|
+
m.set("x", val); // ошибка — m заимствован
|
|
1008
|
+
}
|
|
1009
|
+
m.forEach((k, v) => { ... });
|
|
1010
|
+
for (const k of m.keys()) { ... }
|
|
1011
|
+
for (const v of m.values()) { ... }
|
|
1012
|
+
for (const [k, v] of m.entries()) { ... }
|
|
1013
|
+
```
|
|
1014
|
+
|
|
1015
|
+
##### Set
|
|
1016
|
+
|
|
1017
|
+
Инициализация:
|
|
1018
|
+
```typescript
|
|
1019
|
+
let s = new Set<i32>([1, 2, 3]);
|
|
1020
|
+
let s = new Set<string>();
|
|
1021
|
+
```
|
|
1022
|
+
|
|
1023
|
+
Методы:
|
|
1024
|
+
```typescript
|
|
1025
|
+
s.add(value) // move — Set становится владельцем; бросает при OOM
|
|
1026
|
+
s.has(value) // Ref<T> — только для сравнения, владение не меняется; boolean
|
|
1027
|
+
s.delete(value) // Ref<T> для поиска, возвращает T | null (owned) — элемент удалён из Set
|
|
1028
|
+
s.clear() // void
|
|
1029
|
+
s.size // number, readonly
|
|
1030
|
+
|
|
1031
|
+
// ?. и ?? с Set
|
|
1032
|
+
const deleted = s.delete(user);
|
|
1033
|
+
deleted?.cleanup(); // вызвать метод если элемент был в Set
|
|
1034
|
+
const u = s.delete(user) ?? fallback; // дефолт если элемента не было
|
|
1035
|
+
```
|
|
1036
|
+
|
|
1037
|
+
Примеры ownership:
|
|
1038
|
+
```typescript
|
|
1039
|
+
let s = new Set<User>();
|
|
1040
|
+
let user = new User();
|
|
1041
|
+
s.add(user); // move — user перешёл во владение Set
|
|
1042
|
+
console.log(user); // ошибка: user перемещён
|
|
1043
|
+
|
|
1044
|
+
// примитивы — всегда copy
|
|
1045
|
+
let s = new Set<i32>();
|
|
1046
|
+
let x = 42;
|
|
1047
|
+
s.add(x); // copy
|
|
1048
|
+
console.log(x); // ok
|
|
1049
|
+
```
|
|
1050
|
+
|
|
1051
|
+
Теоретико-множественные операции — доступны для примитивов, `string` и `Shared<T>`:
|
|
1052
|
+
```typescript
|
|
1053
|
+
s.union(other) // новый owned Set — все элементы из s и other
|
|
1054
|
+
s.intersection(other) // новый owned Set — только общие элементы
|
|
1055
|
+
s.difference(other) // новый owned Set — элементы s которых нет в other
|
|
1056
|
+
s.symmetricDifference(other) // новый owned Set — элементы только в одном из двух
|
|
1057
|
+
s.isSubsetOf(other) // boolean
|
|
1058
|
+
s.isSupersetOf(other) // boolean
|
|
1059
|
+
s.isDisjointFrom(other) // boolean
|
|
1060
|
+
```
|
|
1061
|
+
|
|
1062
|
+
Для `Shared<T>` — union это просто retain на каждый элемент, без копирования объектов:
|
|
1063
|
+
```typescript
|
|
1064
|
+
let user1: Shared<User> = new User();
|
|
1065
|
+
let user2: Shared<User> = new User();
|
|
1066
|
+
|
|
1067
|
+
let a = new Set<Shared<User>>([user1, user2]);
|
|
1068
|
+
let b = new Set<Shared<User>>([user2]);
|
|
1069
|
+
let c = a.union(b); // ok — retain на элементы, refcount растёт
|
|
1070
|
+
```
|
|
1071
|
+
|
|
1072
|
+
Для `string` — элементы клонируются в новый Set:
|
|
1073
|
+
```typescript
|
|
1074
|
+
let morphemes = new Set<string>(["бег", "ать"]);
|
|
1075
|
+
let suffixes = new Set<string>(["ать", "ить"]);
|
|
1076
|
+
let common = morphemes.intersection(suffixes); // new Set<string> {"ать"}
|
|
1077
|
+
```
|
|
1078
|
+
|
|
1079
|
+
Для owned сложных типов — ошибка компилятора:
|
|
1080
|
+
```typescript
|
|
1081
|
+
let a = new Set<User>([user1, user2]);
|
|
1082
|
+
let b = new Set<User>([user2]);
|
|
1083
|
+
let c = a.union(b);
|
|
1084
|
+
// ошибка: union requires Set<primitive>, Set<string> or Set<Shared<T>>
|
|
1085
|
+
// hint: use Set<Shared<User>> instead
|
|
1086
|
+
```
|
|
1087
|
+
|
|
1088
|
+
Итерация — `v` это `Ref<T>` для сложных типов, copy для примитивов:
|
|
1089
|
+
```typescript
|
|
1090
|
+
for (const v of s) {
|
|
1091
|
+
v.doSomething(); // ok — immutable метод
|
|
1092
|
+
v.mutMethod(); // ошибка — v это Ref
|
|
1093
|
+
s.add(other); // ошибка — s заимствован
|
|
1094
|
+
}
|
|
1095
|
+
s.forEach((v) => { ... });
|
|
1096
|
+
for (const v of s.values()) { ... }
|
|
1097
|
+
```
|
|
1098
|
+
|
|
1099
|
+
##### Object
|
|
1100
|
+
|
|
1101
|
+
Статические методы для работы с объектами. Ключи — compile-time константы, возвращаются как копии. Значения — Ref для сложных типов, copy для примитивов:
|
|
1102
|
+
|
|
1103
|
+
```typescript
|
|
1104
|
+
const obj = { a: user1, b: user2 };
|
|
1105
|
+
Object.keys(obj) // string[] — копии ключей
|
|
1106
|
+
Object.values(obj) // Ref<User>[] — borrow значений
|
|
1107
|
+
Object.entries(obj) // [string, Ref<User>][] — ключи copy, значения Ref
|
|
1108
|
+
|
|
1109
|
+
const obj = { x: 1, y: 2 };
|
|
1110
|
+
Object.keys(obj) // string[] — копии ключей
|
|
1111
|
+
Object.values(obj) // i32[] — copy (примитивы)
|
|
1112
|
+
Object.entries(obj) // [string, i32][] — всё copy
|
|
1113
|
+
```
|
|
1114
|
+
|
|
1115
|
+
Итерация:
|
|
1116
|
+
```typescript
|
|
1117
|
+
for (const k of Object.keys(obj)) { ... }
|
|
1118
|
+
for (const v of Object.values(obj)) { ... }
|
|
1119
|
+
for (const [k, v] of Object.entries(obj)) { ... }
|
|
1120
|
+
```
|
|
1121
|
+
|
|
1122
|
+
### Memory Model
|
|
1123
|
+
|
|
1124
|
+
**Гибридная модель:** статический ownership/borrow checker + опциональный ARC. Нет GC, нет ручного `free`.
|
|
1125
|
+
|
|
1126
|
+
#### Типы владения
|
|
1127
|
+
|
|
1128
|
+
| Тип | Семантика |
|
|
1129
|
+
|-----|-----------|
|
|
1130
|
+
| `T` | **Owner** — владеет объектом, move при передаче |
|
|
1131
|
+
| `Ref<T>` | **Immutable borrow** — только чтение |
|
|
1132
|
+
| `Mut<T>` | **Mutable borrow** — чтение и запись |
|
|
1133
|
+
| `Shared<T>` | **ARC** — strong ref, увеличивает refcount |
|
|
1134
|
+
| `Weak<T>` | **Weak ref** — не увеличивает refcount, разрывает циклы |
|
|
1135
|
+
|
|
1136
|
+
#### Базовые правила
|
|
1137
|
+
|
|
1138
|
+
- **Примитивы** (`i8`..`i64`, `u8`..`u64`, `f32`, `f64`, `boolean`) — всегда **копируются**, borrow checker не применяется; `T | null` компилируется в struct с флагом
|
|
1139
|
+
- **Сложные типы** (массивы, объекты, строки, классы) — управляются ownership системой
|
|
1140
|
+
|
|
1141
|
+
#### Owner (T) — владение
|
|
1142
|
+
|
|
1143
|
+
##### Move при присвоении
|
|
1144
|
+
|
|
1145
|
+
```typescript
|
|
1146
|
+
let a = new User();
|
|
1147
|
+
let b = a; // MOVE: a теперь invalid
|
|
1148
|
+
// console.log(a); // ошибка: a перемещён
|
|
1149
|
+
```
|
|
1150
|
+
|
|
1151
|
+
##### Move при передаче в функцию
|
|
1152
|
+
|
|
1153
|
+
```typescript
|
|
1154
|
+
function addToCache(cache: Mut<Cache>, data: User[]) {
|
|
1155
|
+
cache.items.push(data); // ok — data принадлежит функции
|
|
1156
|
+
}
|
|
1157
|
+
|
|
1158
|
+
addToCache(myCache, myData);
|
|
1159
|
+
console.log(myData); // ошибка: myData перемещён
|
|
1160
|
+
```
|
|
1161
|
+
|
|
1162
|
+
#### Ref\<T\> — immutable borrow
|
|
1163
|
+
|
|
1164
|
+
Только чтение, без изменения и удаления.
|
|
1165
|
+
|
|
1166
|
+
```typescript
|
|
1167
|
+
function sum(arr: Ref<i32[]>): i32 { ... }
|
|
1168
|
+
|
|
1169
|
+
const data = [1, 2, 3];
|
|
1170
|
+
sum(data);
|
|
1171
|
+
console.log(data); // ok — data не перемещён
|
|
1172
|
+
```
|
|
1173
|
+
|
|
1174
|
+
#### Mut\<T\> — mutable borrow
|
|
1175
|
+
|
|
1176
|
+
Чтение и запись, только один `Mut` за раз.
|
|
1177
|
+
|
|
1178
|
+
```typescript
|
|
1179
|
+
function push(arr: Mut<i32[]>, val: i32) {
|
|
1180
|
+
arr.push(val);
|
|
1181
|
+
}
|
|
1182
|
+
|
|
1183
|
+
let data = [1, 2, 3];
|
|
1184
|
+
push(data, 4);
|
|
1185
|
+
console.log(data); // [1, 2, 3, 4]
|
|
1186
|
+
```
|
|
1187
|
+
|
|
1188
|
+
#### Shared\<T\> — ARC
|
|
1189
|
+
|
|
1190
|
+
Для графов, циклов, неопределённого времени жизни.
|
|
1191
|
+
|
|
1192
|
+
Объект становится `Shared` через аннотацию типа — компилятор автоматически оборачивает в ARC:
|
|
1193
|
+
|
|
1194
|
+
```typescript
|
|
1195
|
+
let node: Shared<Node> = new Node(); // ARC
|
|
1196
|
+
let node = new Node(); // Owner — move семантика
|
|
1197
|
+
```
|
|
1198
|
+
|
|
1199
|
+
Цикл разрывается через `Weak<T>` — одна из сторон держит слабую ссылку:
|
|
1200
|
+
|
|
1201
|
+
```typescript
|
|
1202
|
+
class Node {
|
|
1203
|
+
next: Shared<Node>;
|
|
1204
|
+
prev: Weak<Node>;
|
|
1205
|
+
}
|
|
1206
|
+
|
|
1207
|
+
let node1: Shared<Node> = new Node();
|
|
1208
|
+
let node2: Shared<Node> = new Node();
|
|
1209
|
+
node1.next = node2; // retain(node2)
|
|
1210
|
+
node2.prev = node1; // weak — refcount не растёт, цикл разорван
|
|
1211
|
+
```
|
|
1212
|
+
|
|
1213
|
+
При обращении к `Weak<T>` — тип всегда `T | null` (объект мог быть освобождён). Используй `?.` и `??`:
|
|
1214
|
+
|
|
1215
|
+
```typescript
|
|
1216
|
+
node2.prev?.doSomething(); // вызов только если объект жив
|
|
1217
|
+
const name = node2.prev?.name ?? ""; // дефолт если объект освобождён
|
|
1218
|
+
if (node2.prev != null) {
|
|
1219
|
+
// narrowing — здесь prev: Weak<Node> (жив)
|
|
1220
|
+
}
|
|
1221
|
+
```
|
|
1222
|
+
|
|
1223
|
+
Генерируемый C:
|
|
1224
|
+
```c
|
|
1225
|
+
Node* node1 = Node_new();
|
|
1226
|
+
RC_retain(node1); // refcount = 1
|
|
1227
|
+
Node* node2 = Node_new();
|
|
1228
|
+
RC_retain(node2); // refcount = 1
|
|
1229
|
+
node1->next = node2;
|
|
1230
|
+
RC_retain(node2); // refcount = 2
|
|
1231
|
+
node2->prev = node1; // weak — RC_retain не вызывается
|
|
1232
|
+
```
|
|
1233
|
+
|
|
1234
|
+
#### Правила Borrow Checker
|
|
1235
|
+
|
|
1236
|
+
1. **Нельзя два Mut одновременно**
|
|
1237
|
+
```typescript
|
|
1238
|
+
let a = [1, 2, 3];
|
|
1239
|
+
let r1: Mut<i32[]> = a;
|
|
1240
|
+
let r2: Mut<i32[]> = a; // ошибка: уже есть активный Mut
|
|
1241
|
+
```
|
|
1242
|
+
|
|
1243
|
+
2. **Нельзя Mut + Ref одновременно**
|
|
1244
|
+
```typescript
|
|
1245
|
+
let a = [1, 2, 3];
|
|
1246
|
+
let r1: Ref<i32[]> = a;
|
|
1247
|
+
let r2: Mut<i32[]> = a; // ошибка: a уже заимствован как Ref
|
|
1248
|
+
```
|
|
1249
|
+
|
|
1250
|
+
3. **Можно несколько Ref одновременно**
|
|
1251
|
+
```typescript
|
|
1252
|
+
let a = [1, 2, 3];
|
|
1253
|
+
let r1: Ref<i32[]> = a;
|
|
1254
|
+
let r2: Ref<i32[]> = a; // ok
|
|
1255
|
+
```
|
|
1256
|
+
|
|
1257
|
+
#### Правила передачи аргументов в функцию
|
|
1258
|
+
|
|
1259
|
+
Тип параметра в сигнатуре **полностью диктует semantics на callsite** — явных `&` или `*` не нужно.
|
|
1260
|
+
|
|
1261
|
+
**Примитивы — всегда copy**, независимо от типа параметра:
|
|
1262
|
+
```typescript
|
|
1263
|
+
function foo(x: i32): void { ... }
|
|
1264
|
+
let n = 42;
|
|
1265
|
+
foo(n); // copy — n жив после вызова
|
|
1266
|
+
```
|
|
1267
|
+
|
|
1268
|
+
**Сложные типы — 4 варианта параметра:**
|
|
1269
|
+
```typescript
|
|
1270
|
+
function toRef(x: Ref<User>): void { ... } // borrow
|
|
1271
|
+
function toMut(x: Mut<User>): void { ... } // mutable borrow
|
|
1272
|
+
function toOwned(x: User): void { ... } // move
|
|
1273
|
+
function toShared(x: Shared<User>): void { ... } // retain
|
|
1274
|
+
|
|
1275
|
+
let u = new User();
|
|
1276
|
+
const c = new User();
|
|
1277
|
+
let s: Shared<User> = new User();
|
|
1278
|
+
|
|
1279
|
+
toRef(u); // ok — auto borrow, u жив
|
|
1280
|
+
toRef(c); // ok — auto borrow, c жив
|
|
1281
|
+
toMut(u); // ok — auto mutable borrow
|
|
1282
|
+
toMut(c); // ошибка: нельзя Mut<T> из const
|
|
1283
|
+
toOwned(u); // ok — move, u недоступен после вызова
|
|
1284
|
+
toOwned(c); // ошибка: нельзя move из const
|
|
1285
|
+
toShared(s); // ok — retain (refcount++)
|
|
1286
|
+
toShared(u); // ошибка: u не является Shared<T>
|
|
1287
|
+
```
|
|
1288
|
+
|
|
1289
|
+
**Передача через промежуточный тип (Ref/Mut/Shared как источник):**
|
|
1290
|
+
```typescript
|
|
1291
|
+
function bar(u: Ref<User>): void {
|
|
1292
|
+
toRef(u); // ok — re-borrow
|
|
1293
|
+
toMut(u); // ошибка: Ref → Mut запрещено
|
|
1294
|
+
toOwned(u); // ошибка: нельзя move из Ref
|
|
1295
|
+
// hint: clone если User implements Clone
|
|
1296
|
+
}
|
|
1297
|
+
|
|
1298
|
+
function baz(u: Mut<User>): void {
|
|
1299
|
+
toRef(u); // ok — Mut → Ref разрешено (понижение)
|
|
1300
|
+
toMut(u); // ok — re-borrow как Mut
|
|
1301
|
+
toOwned(u); // ошибка: нельзя move из Mut
|
|
1302
|
+
}
|
|
1303
|
+
|
|
1304
|
+
function qux(u: Shared<User>): void {
|
|
1305
|
+
toRef(u); // ok — borrow из Shared
|
|
1306
|
+
toMut(u); // ошибка: Shared не даёт Mut (нет эксклюзивного владения)
|
|
1307
|
+
toOwned(u); // ошибка: нельзя move из Shared
|
|
1308
|
+
toShared(u); // ok — retain
|
|
1309
|
+
}
|
|
1310
|
+
```
|
|
1311
|
+
|
|
1312
|
+
**Матрица совместимости:**
|
|
1313
|
+
|
|
1314
|
+
| Источник ↓ \ Параметр → | `Ref<T>` | `Mut<T>` | `T` (owned) | `Shared<T>` |
|
|
1315
|
+
|--------------------------|----------|----------|-------------|-------------|
|
|
1316
|
+
| `let T` | ✅ auto borrow | ✅ auto mut borrow | ✅ move | ❌ |
|
|
1317
|
+
| `const T` | ✅ auto borrow | ❌ | ❌ | ❌ |
|
|
1318
|
+
| `Ref<T>` | ✅ re-borrow | ❌ | ❌ | ❌ |
|
|
1319
|
+
| `Mut<T>` | ✅ понижение | ✅ re-borrow | ❌ | ❌ |
|
|
1320
|
+
| `Shared<T>` | ✅ borrow | ❌ | ❌ | ✅ retain |
|
|
1321
|
+
|
|
1322
|
+
#### Scope Constraint (без lifetime аннотаций)
|
|
1323
|
+
|
|
1324
|
+
Два простых правила:
|
|
1325
|
+
|
|
1326
|
+
1. **Ref/Mut нельзя в глобал**
|
|
1327
|
+
```typescript
|
|
1328
|
+
let global: Ref<User>; // ошибка
|
|
1329
|
+
|
|
1330
|
+
function foo(u: Ref<User>) {
|
|
1331
|
+
global = u; // ошибка: borrow не может пережить функцию
|
|
1332
|
+
}
|
|
1333
|
+
```
|
|
1334
|
+
|
|
1335
|
+
2. **Нельзя вернуть ссылку на локал**
|
|
1336
|
+
```typescript
|
|
1337
|
+
function bad(): Ref<User> {
|
|
1338
|
+
const u = new User();
|
|
1339
|
+
return u; // ошибка: u умрёт в конце функции
|
|
1340
|
+
}
|
|
1341
|
+
```
|
|
1342
|
+
|
|
1343
|
+
#### Автоматический Drop
|
|
1344
|
+
|
|
1345
|
+
|
|
1346
|
+
Компилятор вставляет `free()` в конце scope владельца:
|
|
1347
|
+
|
|
1348
|
+
```c
|
|
1349
|
+
{
|
|
1350
|
+
User* b = User_new();
|
|
1351
|
+
// ... логика ...
|
|
1352
|
+
User_free(b); // вставлено автоматически
|
|
1353
|
+
}
|
|
1354
|
+
```
|
|
1355
|
+
|
|
1356
|
+
При множественных `return` — единая точка очистки:
|
|
1357
|
+
|
|
1358
|
+
```c
|
|
1359
|
+
void process(User* u) {
|
|
1360
|
+
if (!u) goto cleanup;
|
|
1361
|
+
if (error) goto cleanup;
|
|
1362
|
+
// ... работа ...
|
|
1363
|
+
cleanup:
|
|
1364
|
+
if (u_is_owned) User_free(u);
|
|
1365
|
+
}
|
|
1366
|
+
```
|
|
1367
|
+
|
|
1368
|
+
#### Clone
|
|
1369
|
+
|
|
1370
|
+
`Clone` — интерфейс для deep copy. Два синтаксиса, одна семантика:
|
|
1371
|
+
|
|
1372
|
+
```typescript
|
|
1373
|
+
interface Clone {
|
|
1374
|
+
clone(): this;
|
|
1375
|
+
}
|
|
1376
|
+
|
|
1377
|
+
class User implements Clone {
|
|
1378
|
+
name: string;
|
|
1379
|
+
age: i32;
|
|
1380
|
+
|
|
1381
|
+
clone(): User {
|
|
1382
|
+
return new User(this.name, this.age);
|
|
1383
|
+
}
|
|
1384
|
+
}
|
|
1385
|
+
|
|
1386
|
+
let u1 = new User("Alice", 30);
|
|
1387
|
+
let u2 = structuredClone(u1); // функциональный стиль
|
|
1388
|
+
let u3 = u1.clone(); // метод — то же самое
|
|
1389
|
+
console.log(u1); // ok — u1 жив
|
|
1390
|
+
```
|
|
1391
|
+
|
|
1392
|
+
- Примитивы и `string` — auto-implement Clone
|
|
1393
|
+
- Массивы — `clone()` / `structuredClone` работают если элементы реализуют `Clone`
|
|
1394
|
+
- `Shared<T>` — `structuredClone` создаёт новый независимый объект (deep copy, не retain)
|
|
1395
|
+
- Spread для pure-primitive структур = неявный clone; для сложных полей = move
|
|
1396
|
+
|
|
1397
|
+
```typescript
|
|
1398
|
+
// массивы
|
|
1399
|
+
let arr = [1, 2, 3];
|
|
1400
|
+
let arr2 = arr.clone(); // ok — примитивы
|
|
1401
|
+
|
|
1402
|
+
let users = [user1, user2];
|
|
1403
|
+
let users2 = users.clone(); // ok — User implements Clone
|
|
1404
|
+
|
|
1405
|
+
let items = [item1, item2];
|
|
1406
|
+
let items2 = items.clone(); // ошибка: Item does not implement Clone
|
|
1407
|
+
// hint: implement Clone on Item
|
|
1408
|
+
```
|
|
1409
|
+
|
|
1410
|
+
#### Type Aliases
|
|
1411
|
+
|
|
1412
|
+
`type` — compile-time алиас, не генерирует новый тип в C:
|
|
1413
|
+
|
|
1414
|
+
```typescript
|
|
1415
|
+
// 1. Алиас примитива — читабельность
|
|
1416
|
+
type UserId = i32;
|
|
1417
|
+
type Timestamp = i64;
|
|
1418
|
+
|
|
1419
|
+
function getUser(id: UserId): User { ... } // UserId = i32 в C
|
|
1420
|
+
|
|
1421
|
+
// 2. Алиас объекта — эквивалентен data-only interface, генерирует typedef struct
|
|
1422
|
+
type Point = { x: f64, y: f64 }; // → typedef struct { double x; double y; } Point;
|
|
1423
|
+
let p: Point = { x: 1.0, y: 2.0 }; // ok — Point struct
|
|
1424
|
+
|
|
1425
|
+
// 3. Union тип
|
|
1426
|
+
type StringOrInt = string | i32;
|
|
1427
|
+
type Nullable<T> = T | null; // generic алиас
|
|
1428
|
+
|
|
1429
|
+
function process(x: StringOrInt): void { ... }
|
|
1430
|
+
|
|
1431
|
+
// 4. Тип функции — для колбэков
|
|
1432
|
+
type Callback = (x: i32) => void;
|
|
1433
|
+
type Comparator<T> = (a: Ref<T>, b: Ref<T>) => i32;
|
|
1434
|
+
|
|
1435
|
+
function sort(arr: Mut<i32[]>, cmp: Comparator<i32>): void { ... }
|
|
1436
|
+
```
|
|
1437
|
+
|
|
1438
|
+
- `type Point = { ... }` = `interface Point { ... }` для data-only структур — оба генерируют `typedef struct Point` в C
|
|
1439
|
+
- `type UserId = i32` — compile-time алиас примитива, нового C типа нет
|
|
1440
|
+
- `type StringOrInt = string | i32` — compile-time union, нового C типа нет
|
|
1441
|
+
- Номинальная типизация: `type UserId = i32` и `i32` — разные типы
|
|
1442
|
+
|
|
1443
|
+
#### Enum
|
|
1444
|
+
|
|
1445
|
+
##### Числовой enum
|
|
1446
|
+
|
|
1447
|
+
```typescript
|
|
1448
|
+
enum Direction { North, South, East, West } // 0, 1, 2, 3
|
|
1449
|
+
enum Color { Red = 1, Green = 2, Blue = 4 } // явные значения (битовые флаги)
|
|
1450
|
+
```
|
|
1451
|
+
|
|
1452
|
+
C-output:
|
|
1453
|
+
```c
|
|
1454
|
+
typedef enum { North, South, East, West } Direction;
|
|
1455
|
+
static const Direction Direction_values[] = { North, South, East, West };
|
|
1456
|
+
static const char* Direction_names[] = { "North", "South", "East", "West" };
|
|
1457
|
+
```
|
|
1458
|
+
|
|
1459
|
+
##### Строковый enum
|
|
1460
|
+
|
|
1461
|
+
```typescript
|
|
1462
|
+
enum Status { Ok = "OK", Fail = "FAIL", Pending = "PENDING" }
|
|
1463
|
+
```
|
|
1464
|
+
|
|
1465
|
+
C-output:
|
|
1466
|
+
```c
|
|
1467
|
+
typedef enum { Status_Ok, Status_Fail, Status_Pending } Status;
|
|
1468
|
+
static const char* Status_strings[] = { "OK", "FAIL", "PENDING" };
|
|
1469
|
+
```
|
|
1470
|
+
|
|
1471
|
+
##### const enum
|
|
1472
|
+
|
|
1473
|
+
Только C enum, без runtime таблиц. Используется когда важен размер бинаря (embedded).
|
|
1474
|
+
|
|
1475
|
+
```typescript
|
|
1476
|
+
const enum Pin { PA0 = 0, PA1 = 1, PB0 = 8, PB1 = 9 }
|
|
1477
|
+
```
|
|
1478
|
+
|
|
1479
|
+
C-output:
|
|
1480
|
+
```c
|
|
1481
|
+
typedef enum { PA0 = 0, PA1 = 1, PB0 = 8, PB1 = 9 } Pin;
|
|
1482
|
+
// больше ничего — нет таблиц
|
|
1483
|
+
```
|
|
1484
|
+
|
|
1485
|
+
Утилиты на `const enum` недоступны — ошибка компилятора:
|
|
1486
|
+
```typescript
|
|
1487
|
+
Pin.values() // error: const enum has no runtime table
|
|
1488
|
+
Pin.fromValue(0) // error: const enum has no runtime table
|
|
1489
|
+
Pin.PA0.toString() // error: const enum has no runtime table
|
|
1490
|
+
```
|
|
1491
|
+
|
|
1492
|
+
##### Утилиты enum (только обычный enum)
|
|
1493
|
+
|
|
1494
|
+
```typescript
|
|
1495
|
+
enum Direction { North, South, East, West }
|
|
1496
|
+
|
|
1497
|
+
Direction.values() // Direction[] — все значения: [North, South, East, West]
|
|
1498
|
+
Direction.fromValue(2) // Direction | null — Direction.East | null если не найдено
|
|
1499
|
+
Direction.North.toString() // string — "North"
|
|
1500
|
+
|
|
1501
|
+
// использование
|
|
1502
|
+
for (const d of Direction.values()) {
|
|
1503
|
+
console.log(d.toString());
|
|
1504
|
+
}
|
|
1505
|
+
|
|
1506
|
+
const d = Direction.fromValue(userInput);
|
|
1507
|
+
if (d != null) {
|
|
1508
|
+
console.log(d.toString());
|
|
1509
|
+
}
|
|
1510
|
+
```
|
|
1511
|
+
|
|
1512
|
+
##### enum в switch / match
|
|
1513
|
+
|
|
1514
|
+
```typescript
|
|
1515
|
+
// switch — компилятор выдаёт warning если не все значения покрыты
|
|
1516
|
+
switch (dir) {
|
|
1517
|
+
case Direction.North: ...; break;
|
|
1518
|
+
case Direction.South: ...; break;
|
|
1519
|
+
case Direction.East: ...; break;
|
|
1520
|
+
case Direction.West: ...; break;
|
|
1521
|
+
}
|
|
1522
|
+
|
|
1523
|
+
// match — ошибка компилятора если не все значения покрыты (exhaustiveness)
|
|
1524
|
+
const label = match (dir) {
|
|
1525
|
+
Direction.North => "вверх",
|
|
1526
|
+
Direction.South => "вниз",
|
|
1527
|
+
Direction.East => "вправо",
|
|
1528
|
+
Direction.West => "влево",
|
|
1529
|
+
// _ не нужен — все случаи покрыты
|
|
1530
|
+
};
|
|
1531
|
+
```
|
|
1532
|
+
|
|
1533
|
+
##### enum vs const enum
|
|
1534
|
+
|
|
1535
|
+
| | `enum` | `const enum` |
|
|
1536
|
+
|---|---|---|
|
|
1537
|
+
| C-output | `typedef enum` + таблицы | только `typedef enum` |
|
|
1538
|
+
| `.values()` | ✅ | ❌ |
|
|
1539
|
+
| `.fromValue()` | ✅ | ❌ |
|
|
1540
|
+
| `.toString()` | ✅ | ❌ |
|
|
1541
|
+
| Размер бинаря | больше | минимальный |
|
|
1542
|
+
| Применение | общий случай | embedded, флаги, константы |
|
|
1543
|
+
|
|
1544
|
+
#### Интерфейсы
|
|
1545
|
+
|
|
1546
|
+
Два назначения:
|
|
1547
|
+
|
|
1548
|
+
**1. Данные без методов** — компилируется в `typedef struct`:
|
|
1549
|
+
```typescript
|
|
1550
|
+
interface Point {
|
|
1551
|
+
x: f64;
|
|
1552
|
+
y: f64;
|
|
1553
|
+
}
|
|
1554
|
+
|
|
1555
|
+
let p: Point = { x: 10.5, y: 20.3 };
|
|
1556
|
+
```
|
|
1557
|
+
```c
|
|
1558
|
+
typedef struct { double x; double y; } Point;
|
|
1559
|
+
```
|
|
1560
|
+
|
|
1561
|
+
**2. Контракт с методами** — компилируется в vtable (fat pointer, как `dyn Trait` в Rust):
|
|
1562
|
+
```typescript
|
|
1563
|
+
interface Drawable {
|
|
1564
|
+
draw(): void;
|
|
1565
|
+
mut resize(factor: f64): void;
|
|
1566
|
+
}
|
|
1567
|
+
|
|
1568
|
+
class Circle implements Drawable {
|
|
1569
|
+
draw(): void { ... }
|
|
1570
|
+
mut resize(factor: f64): void { ... }
|
|
1571
|
+
}
|
|
1572
|
+
|
|
1573
|
+
let shape: Drawable = new Circle(); // fat pointer: self + vtable
|
|
1574
|
+
shape = new Rect(); // ok — другой тип, та же переменная
|
|
1575
|
+
shape.draw(); // ok — immutable метод
|
|
1576
|
+
shape.resize(2.0); // ok — mut метод, shape это let
|
|
1577
|
+
|
|
1578
|
+
const shape2: Drawable = new Circle();
|
|
1579
|
+
shape2.draw(); // ok
|
|
1580
|
+
shape2.resize(2.0); // ошибка: нельзя вызвать mut метод на const
|
|
1581
|
+
```
|
|
1582
|
+
```c
|
|
1583
|
+
typedef struct {
|
|
1584
|
+
void (*draw)(void* self);
|
|
1585
|
+
void (*resize)(void* self, double factor);
|
|
1586
|
+
} Drawable_vtable;
|
|
1587
|
+
|
|
1588
|
+
typedef struct {
|
|
1589
|
+
void* self;
|
|
1590
|
+
Drawable_vtable* vtable;
|
|
1591
|
+
} Drawable;
|
|
1592
|
+
```
|
|
1593
|
+
|
|
1594
|
+
- Класс может реализовывать несколько интерфейсов: `class Foo implements A, B`
|
|
1595
|
+
- `mut` методы интерфейса подчиняются тем же правилам что и `mut` методы класса: `const` переменная запрещает вызов, `let` — разрешает
|
|
1596
|
+
```typescript
|
|
1597
|
+
interface Drawable {
|
|
1598
|
+
draw(): void;
|
|
1599
|
+
}
|
|
1600
|
+
|
|
1601
|
+
interface Resizable {
|
|
1602
|
+
mut resize(factor: f64): void;
|
|
1603
|
+
}
|
|
1604
|
+
|
|
1605
|
+
class Circle implements Drawable, Resizable {
|
|
1606
|
+
draw(): void { ... }
|
|
1607
|
+
mut resize(factor: f64): void { ... }
|
|
1608
|
+
}
|
|
1609
|
+
|
|
1610
|
+
let shape: Drawable = new Circle(); // ok
|
|
1611
|
+
let resizable: Resizable = new Circle(); // ok
|
|
1612
|
+
```
|
|
1613
|
+
- Если класс не реализует все методы интерфейса — ошибка компилятора
|
|
1614
|
+
|
|
1615
|
+
#### Классы
|
|
1616
|
+
|
|
1617
|
+
**Наследования нет** — только композиция. `extends` отсутствует.
|
|
1618
|
+
|
|
1619
|
+
```typescript
|
|
1620
|
+
// вместо наследования — композиция
|
|
1621
|
+
class Animal {
|
|
1622
|
+
name: string;
|
|
1623
|
+
mut speak(): string { ... }
|
|
1624
|
+
}
|
|
1625
|
+
|
|
1626
|
+
class Dog {
|
|
1627
|
+
animal: Animal; // композиция
|
|
1628
|
+
breed: string;
|
|
1629
|
+
}
|
|
1630
|
+
```
|
|
1631
|
+
|
|
1632
|
+
`mut` определяет семантику `this`. Модификаторы методов и полей:
|
|
1633
|
+
|
|
1634
|
+
| Модификатор | Описание |
|
|
1635
|
+
|-------------|----------|
|
|
1636
|
+
| `public` | виден везде (по умолчанию) |
|
|
1637
|
+
| `private` | виден только внутри класса |
|
|
1638
|
+
| `static` | метод на классе, нет `this` |
|
|
1639
|
+
| `mut` | `this` — `Mut<Self>`, иначе `Ref<Self>` |
|
|
1640
|
+
| `move` | `this` — `Self` (owned), объект перемещается в метод при вызове |
|
|
1641
|
+
|
|
1642
|
+
```typescript
|
|
1643
|
+
class Counter {
|
|
1644
|
+
private value: i32 = 0;
|
|
1645
|
+
|
|
1646
|
+
public get(): i32 { // this — Ref<Counter>
|
|
1647
|
+
return this.value;
|
|
1648
|
+
}
|
|
1649
|
+
|
|
1650
|
+
public mut increment(): void { // this — Mut<Counter>
|
|
1651
|
+
this.value++;
|
|
1652
|
+
}
|
|
1653
|
+
|
|
1654
|
+
private mut reset(): void { // private mutable
|
|
1655
|
+
this.value = 0;
|
|
1656
|
+
}
|
|
1657
|
+
|
|
1658
|
+
static create(): Counter { // static — нет this
|
|
1659
|
+
return new Counter();
|
|
1660
|
+
}
|
|
1661
|
+
|
|
1662
|
+
private static default(): Counter { // private static
|
|
1663
|
+
return new Counter();
|
|
1664
|
+
}
|
|
1665
|
+
}
|
|
1666
|
+
|
|
1667
|
+
const c = new Counter();
|
|
1668
|
+
c.get(); // ok
|
|
1669
|
+
c.increment(); // ошибка: нельзя вызвать mut метод на const
|
|
1670
|
+
|
|
1671
|
+
let c2 = new Counter();
|
|
1672
|
+
c2.increment(); // ok
|
|
1673
|
+
```
|
|
1674
|
+
|
|
1675
|
+
- `static` + `mut` — недопустимо, ошибка компилятора (нет `this`)
|
|
1676
|
+
- `protected` — отсутствует (нет наследования)
|
|
1677
|
+
|
|
1678
|
+
#### Семантика `this` и доступ к полям
|
|
1679
|
+
|
|
1680
|
+
Тип `this` определяет тип `this.field`. Затем применяются **те же правила передачи аргументов** что и для обычных функций — см. матрицу совместимости в разделе "Правила передачи аргументов в функцию":
|
|
1681
|
+
|
|
1682
|
+
| Вид метода | `this` тип | `this.field` тип (сложный) | `this.field` тип (примитив) |
|
|
1683
|
+
|-----------|------------|---------------------------|---------------------------|
|
|
1684
|
+
| обычный | `Ref<Self>` | `Ref<T>` | copy |
|
|
1685
|
+
| `mut` | `Mut<Self>` | `Mut<T>` | copy |
|
|
1686
|
+
| `move` | `Self` (owned) | `T` (owned) | copy |
|
|
1687
|
+
|
|
1688
|
+
Тип `this.field` определяется типом `this`. Затем применяются **те же правила из матрицы совместимости** (раздел "Правила передачи аргументов в функцию"):
|
|
1689
|
+
|
|
1690
|
+
```typescript
|
|
1691
|
+
function sendEmail(to: string): void { ... } // ожидает owned string
|
|
1692
|
+
function printRef(s: Ref<string>): void { ... } // ожидает borrow
|
|
1693
|
+
|
|
1694
|
+
class QueryBuilder {
|
|
1695
|
+
query: string;
|
|
1696
|
+
params: i32[];
|
|
1697
|
+
|
|
1698
|
+
// обычный метод — this: Ref<Self>, this.query: Ref<string>
|
|
1699
|
+
preview(): void {
|
|
1700
|
+
printRef(this.query); // ok — Ref<string> → Ref<string> ✅
|
|
1701
|
+
sendEmail(this.query); // ошибка — Ref<string> → string ❌
|
|
1702
|
+
// матрица: Ref<T> → T (owned) = запрещено
|
|
1703
|
+
// hint: clone если string implements Clone
|
|
1704
|
+
sendEmail(this.query.clone()); // ok ✅
|
|
1705
|
+
console.log(this.params[0]); // ok — i32 всегда copy ✅
|
|
1706
|
+
}
|
|
1707
|
+
|
|
1708
|
+
// mut метод — this: Mut<Self>, this.query: Mut<string>
|
|
1709
|
+
mut setQuery(q: string): void {
|
|
1710
|
+
this.query = q; // ok — Mut разрешает запись ✅
|
|
1711
|
+
sendEmail(this.query); // ошибка — Mut<string> → string ❌
|
|
1712
|
+
// матрица: Mut<T> → T (owned) = запрещено
|
|
1713
|
+
sendEmail(this.query.clone()); // ok ✅
|
|
1714
|
+
}
|
|
1715
|
+
|
|
1716
|
+
// move метод — this: Self (owned), this.query: string (owned)
|
|
1717
|
+
move build(): Query {
|
|
1718
|
+
return new Query(this.query, this.params); // ok — T → T, move ✅
|
|
1719
|
+
}
|
|
1720
|
+
}
|
|
1721
|
+
|
|
1722
|
+
let b = new QueryBuilder("SELECT *", [1, 2]);
|
|
1723
|
+
b.preview(); // ok — b жив ✅
|
|
1724
|
+
b.setQuery("INSERT"); // ok — b жив ✅
|
|
1725
|
+
const q = b.build(); // ok — b moved в метод
|
|
1726
|
+
console.log(b); // ошибка: b перемещён ❌
|
|
1727
|
+
|
|
1728
|
+
const b2 = new QueryBuilder("SELECT *", []);
|
|
1729
|
+
b2.build(); // ошибка: нельзя вызвать move метод на const ❌
|
|
1730
|
+
```
|
|
1731
|
+
|
|
1732
|
+
`readonly` поле можно записать только в конструкторе:
|
|
1733
|
+
|
|
1734
|
+
```typescript
|
|
1735
|
+
class User {
|
|
1736
|
+
readonly id: i32;
|
|
1737
|
+
name: string;
|
|
1738
|
+
|
|
1739
|
+
constructor(id: i32, name: string) {
|
|
1740
|
+
this.id = id; // ok
|
|
1741
|
+
this.name = name;
|
|
1742
|
+
}
|
|
1743
|
+
|
|
1744
|
+
mut rename(newName: string) {
|
|
1745
|
+
this.name = newName; // ok
|
|
1746
|
+
this.id = 99; // ошибка: readonly
|
|
1747
|
+
}
|
|
1748
|
+
}
|
|
1749
|
+
```
|
|
1750
|
+
|
|
1751
|
+
`mut` метод может менять обычные поля, но не `readonly`.
|
|
1752
|
+
|
|
1753
|
+
`move` метод передает поля объекта наружу без лишнего копирования, когда исходный объект больше не нужен. Паттерн `Builder`:
|
|
1754
|
+
|
|
1755
|
+
```typescript
|
|
1756
|
+
class QueryBuilder {
|
|
1757
|
+
query: string;
|
|
1758
|
+
params: i32[];
|
|
1759
|
+
|
|
1760
|
+
// без move — this: Ref<Self>, поля нельзя move, нужен clone:
|
|
1761
|
+
build(): Query {
|
|
1762
|
+
return new Query(this.query.clone(), this.params.clone()); // лишняя копия данных
|
|
1763
|
+
}
|
|
1764
|
+
|
|
1765
|
+
// с move — this: Self (owned), поля можно move, clone не нужен
|
|
1766
|
+
move build(): Query {
|
|
1767
|
+
return new Query(this.query, this.params); // move полей — экономия памяти
|
|
1768
|
+
}
|
|
1769
|
+
}
|
|
1770
|
+
|
|
1771
|
+
let b = new QueryBuilder("SELECT *", [1, 2, 3]);
|
|
1772
|
+
const q = b.build(); // b перемещён в метод, данные переданы в Query без копии
|
|
1773
|
+
console.log(b); // ошибка: b перемещён — компилятор ловит
|
|
1774
|
+
```
|
|
1775
|
+
|
|
1776
|
+
Конструктор — поля забирают владение (move):
|
|
1777
|
+
|
|
1778
|
+
```typescript
|
|
1779
|
+
class Line {
|
|
1780
|
+
start: Point;
|
|
1781
|
+
end: Point;
|
|
1782
|
+
|
|
1783
|
+
constructor(start: Point, end: Point) {
|
|
1784
|
+
this.start = start; // move
|
|
1785
|
+
this.end = end; // move
|
|
1786
|
+
}
|
|
1787
|
+
}
|
|
1788
|
+
|
|
1789
|
+
const p1 = new Point(0, 0);
|
|
1790
|
+
const p2 = new Point(1, 1);
|
|
1791
|
+
const line = new Line(p1, p2);
|
|
1792
|
+
console.log(p1); // ошибка: p1 перемещён в line
|
|
1793
|
+
```
|
|
1794
|
+
|
|
1795
|
+
Автогенерация конструктора — если конструктор не написан, компилятор генерирует его из полей:
|
|
1796
|
+
|
|
1797
|
+
- Поля **с дефолтом** → параметр со значением по умолчанию
|
|
1798
|
+
- Поля **без дефолта** → обязательный параметр (в порядке объявления)
|
|
1799
|
+
|
|
1800
|
+
```typescript
|
|
1801
|
+
class User {
|
|
1802
|
+
name: string; // нет дефолта → обязательный параметр
|
|
1803
|
+
age: i32 = 0; // есть дефолт → необязательный параметр
|
|
1804
|
+
active: boolean = true;
|
|
1805
|
+
}
|
|
1806
|
+
// компилятор генерирует:
|
|
1807
|
+
// constructor(name: string, age: i32 = 0, active: boolean = true)
|
|
1808
|
+
|
|
1809
|
+
new User("Alice"); // ok — name="Alice", age=0, active=true
|
|
1810
|
+
new User("Alice", 30); // ok — name="Alice", age=30, active=true
|
|
1811
|
+
new User("Alice", 30, false); // ok
|
|
1812
|
+
new User(); // ошибка: name обязателен
|
|
1813
|
+
|
|
1814
|
+
class Point {
|
|
1815
|
+
x: f64 = 0.0;
|
|
1816
|
+
y: f64 = 0.0;
|
|
1817
|
+
// все поля с дефолтом → генерируется конструктор без обязательных параметров
|
|
1818
|
+
}
|
|
1819
|
+
|
|
1820
|
+
let p = new Point(); // ok — x=0.0, y=0.0
|
|
1821
|
+
let p2 = new Point(1.0); // ok — x=1.0, y=0.0
|
|
1822
|
+
```
|
|
1823
|
+
|
|
1824
|
+
Если написан явный `constructor` — автогенерация не происходит.
|
|
1825
|
+
|
|
1826
|
+
Дефолтные параметры конструктора — вместо перегрузки по количеству:
|
|
1827
|
+
```typescript
|
|
1828
|
+
class Point {
|
|
1829
|
+
x: f64;
|
|
1830
|
+
y: f64;
|
|
1831
|
+
|
|
1832
|
+
constructor(x: f64 = 0.0, y: f64 = 0.0) {
|
|
1833
|
+
this.x = x;
|
|
1834
|
+
this.y = y;
|
|
1835
|
+
}
|
|
1836
|
+
}
|
|
1837
|
+
|
|
1838
|
+
let p1 = new Point(); // x=0.0, y=0.0
|
|
1839
|
+
let p2 = new Point(1.0); // x=1.0, y=0.0
|
|
1840
|
+
let p3 = new Point(1.0, 2.0); // x=1.0, y=2.0
|
|
1841
|
+
```
|
|
1842
|
+
|
|
1843
|
+
`private` конструктор — для singleton/factory паттернов:
|
|
1844
|
+
```typescript
|
|
1845
|
+
class Config {
|
|
1846
|
+
private constructor() { ... }
|
|
1847
|
+
|
|
1848
|
+
static create(): Config {
|
|
1849
|
+
return new Config(); // ok — внутри класса
|
|
1850
|
+
}
|
|
1851
|
+
}
|
|
1852
|
+
|
|
1853
|
+
let c = new Config(); // ошибка: конструктор private
|
|
1854
|
+
let c = Config.create(); // ok
|
|
1855
|
+
```
|
|
1856
|
+
|
|
1857
|
+
#### `const` vs `let`
|
|
1858
|
+
|
|
1859
|
+
- `const obj` — нельзя вызывать `mut` методы, нельзя передать как `Mut`, нельзя move
|
|
1860
|
+
- `let obj` — можно всё
|
|
1861
|
+
|
|
1862
|
+
```typescript
|
|
1863
|
+
function foo(c: Mut<Counter>) { c.increment(); }
|
|
1864
|
+
|
|
1865
|
+
const c = new Counter();
|
|
1866
|
+
foo(c); // ошибка: const нельзя передать как Mut
|
|
1867
|
+
|
|
1868
|
+
let c2 = new Counter();
|
|
1869
|
+
foo(c2); // ok
|
|
1870
|
+
|
|
1871
|
+
// move из const — запрещён
|
|
1872
|
+
const arr = [user1, user2];
|
|
1873
|
+
let b = arr; // ошибка: cannot move out of const
|
|
1874
|
+
// hint: use Shared<T> if shared ownership is needed
|
|
1875
|
+
|
|
1876
|
+
const arr2: Shared<User[]> = [user1, user2];
|
|
1877
|
+
let b2 = arr2; // ok — retain, не move
|
|
1878
|
+
```
|
|
1879
|
+
|
|
1880
|
+
#### For-of цикл
|
|
1881
|
+
|
|
1882
|
+
Тип loop-переменной определяется **объявлением** (`const`/`let`), а не источником:
|
|
1883
|
+
|
|
1884
|
+
- `for (const item of ...)` — **всегда** `Ref<T>` для сложных типов, copy для примитивов
|
|
1885
|
+
- `for (let item of ...)` — `Mut<T>`, **только если источник `let`**; если источник `const` — ошибка компилятора
|
|
1886
|
+
|
|
1887
|
+
```typescript
|
|
1888
|
+
const arr = [obj1, obj2, obj3];
|
|
1889
|
+
|
|
1890
|
+
for (const item of arr) { // ok — item: Ref<Obj>
|
|
1891
|
+
item.doSomething(); // ok — immutable метод
|
|
1892
|
+
item.mutMethod(); // ошибка — item это Ref
|
|
1893
|
+
}
|
|
1894
|
+
|
|
1895
|
+
for (let item of arr) { // ошибка: источник const, используй for (const item of arr)
|
|
1896
|
+
}
|
|
1897
|
+
```
|
|
1898
|
+
|
|
1899
|
+
```typescript
|
|
1900
|
+
let arr = [obj1, obj2, obj3];
|
|
1901
|
+
|
|
1902
|
+
for (const item of arr) { // ok — item: Ref<Obj>
|
|
1903
|
+
item.doSomething(); // ok
|
|
1904
|
+
item.mutMethod(); // ошибка — item это Ref
|
|
1905
|
+
}
|
|
1906
|
+
|
|
1907
|
+
for (let item of arr) { // ok — item: Mut<Obj>
|
|
1908
|
+
item.mutMethod(); // ok — изменения попадают в arr
|
|
1909
|
+
arr.push(obj4); // ошибка — arr заимствован во время итерации
|
|
1910
|
+
}
|
|
1911
|
+
```
|
|
1912
|
+
|
|
1913
|
+
Примитивы — `item` всегда копируется независимо от `let`/`const`:
|
|
1914
|
+
|
|
1915
|
+
```typescript
|
|
1916
|
+
let nums = [1, 2, 3];
|
|
1917
|
+
for (let item of nums) {
|
|
1918
|
+
item++; // warning: изменение копии не имеет эффекта
|
|
1919
|
+
}
|
|
1920
|
+
|
|
1921
|
+
// чтобы изменить элементы — используй индекс:
|
|
1922
|
+
for (let i = 0; i < nums.length; i++) {
|
|
1923
|
+
nums[i]++; // ok
|
|
1924
|
+
}
|
|
1925
|
+
```
|
|
1926
|
+
|
|
1927
|
+
#### while / do-while
|
|
1928
|
+
|
|
1929
|
+
```typescript
|
|
1930
|
+
// while — проверка условия до итерации
|
|
1931
|
+
let i = 0;
|
|
1932
|
+
while (i < 10) {
|
|
1933
|
+
console.log(i);
|
|
1934
|
+
i++;
|
|
1935
|
+
}
|
|
1936
|
+
|
|
1937
|
+
// do-while — проверка условия после итерации (тело выполняется минимум 1 раз)
|
|
1938
|
+
let input: string;
|
|
1939
|
+
do {
|
|
1940
|
+
input = readLine();
|
|
1941
|
+
} while (input === "");
|
|
1942
|
+
|
|
1943
|
+
// break и continue работают как в JS
|
|
1944
|
+
while (true) {
|
|
1945
|
+
const line = readLine();
|
|
1946
|
+
if (line === "quit") break;
|
|
1947
|
+
if (line === "") continue;
|
|
1948
|
+
process(line);
|
|
1949
|
+
}
|
|
1950
|
+
```
|
|
1951
|
+
|
|
1952
|
+
- `break` — выход из цикла
|
|
1953
|
+
- `continue` — переход к следующей итерации
|
|
1954
|
+
- Labeled break/continue для вложенных циклов:
|
|
1955
|
+
|
|
1956
|
+
```typescript
|
|
1957
|
+
outer: while (true) {
|
|
1958
|
+
while (true) {
|
|
1959
|
+
if (done) break outer; // выход из внешнего цикла
|
|
1960
|
+
if (skip) continue outer; // следующая итерация внешнего цикла
|
|
1961
|
+
}
|
|
1962
|
+
}
|
|
1963
|
+
```
|
|
1964
|
+
|
|
1965
|
+
##### async/await в циклах
|
|
1966
|
+
|
|
1967
|
+
`await` разрешён внутри любого цикла (`for`, `for-of`, `while`, `do-while`) при условии что функция `async`. Итерации выполняются **последовательно** — следующая итерация начинается только после завершения `await`.
|
|
1968
|
+
|
|
1969
|
+
```typescript
|
|
1970
|
+
// for-of
|
|
1971
|
+
async function processAll(ids: i32[]): void {
|
|
1972
|
+
for (const id of ids) {
|
|
1973
|
+
const user = await fetchUser(id); // ждём каждый запрос по очереди
|
|
1974
|
+
console.log(user.name);
|
|
1975
|
+
}
|
|
1976
|
+
}
|
|
1977
|
+
|
|
1978
|
+
// while
|
|
1979
|
+
async function pollUntilReady(id: i32): Status {
|
|
1980
|
+
while (true) {
|
|
1981
|
+
const status = await checkStatus(id);
|
|
1982
|
+
if (status !== Status.Pending) return status;
|
|
1983
|
+
await delay(500);
|
|
1984
|
+
}
|
|
1985
|
+
}
|
|
1986
|
+
|
|
1987
|
+
// для параллельного выполнения — Promise.all
|
|
1988
|
+
async function processAllParallel(ids: i32[]): void {
|
|
1989
|
+
const users = await Promise.all(ids.map(id => fetchUser(id))); // все запросы параллельно
|
|
1990
|
+
}
|
|
1991
|
+
```
|
|
1992
|
+
|
|
1993
|
+
#### switch / case
|
|
1994
|
+
|
|
1995
|
+
Синтаксис как в JS/TS. **Implicit fallthrough запрещён** — забытый `break` или `return` это ошибка компилятора.
|
|
1996
|
+
|
|
1997
|
+
```typescript
|
|
1998
|
+
switch (status) {
|
|
1999
|
+
case 200:
|
|
2000
|
+
handleOk();
|
|
2001
|
+
break;
|
|
2002
|
+
case 404:
|
|
2003
|
+
handleNotFound();
|
|
2004
|
+
break;
|
|
2005
|
+
case 500:
|
|
2006
|
+
case 503: // группировка case — ok (оба ведут к одному телу)
|
|
2007
|
+
handleError();
|
|
2008
|
+
break;
|
|
2009
|
+
default:
|
|
2010
|
+
handleUnknown();
|
|
2011
|
+
}
|
|
2012
|
+
```
|
|
2013
|
+
|
|
2014
|
+
- `break` или `return` обязательны в каждом `case` — иначе ошибка компилятора
|
|
2015
|
+
- Группировка пустых `case` (`case 500: case 503:`) разрешена
|
|
2016
|
+
- `default` необязателен, но компилятор выдаёт warning если не покрыты все значения enum
|
|
2017
|
+
- Switch работает на: числовых типах, `string`, `boolean`, enum
|
|
2018
|
+
|
|
2019
|
+
#### match
|
|
2020
|
+
|
|
2021
|
+
> Синтаксис соответствует [TC39 Pattern Matching proposal](https://github.com/tc39/proposal-pattern-matching) и ожидаемому TypeScript 7.
|
|
2022
|
+
|
|
2023
|
+
Expression-based pattern matching. Возвращает значение, exhaustiveness проверяется компилятором.
|
|
2024
|
+
|
|
2025
|
+
```typescript
|
|
2026
|
+
// литералы
|
|
2027
|
+
const label = match (x) {
|
|
2028
|
+
0 => "zero",
|
|
2029
|
+
1..10 => "small",
|
|
2030
|
+
11..100 => "medium",
|
|
2031
|
+
_ => "large",
|
|
2032
|
+
};
|
|
2033
|
+
|
|
2034
|
+
// null
|
|
2035
|
+
const msg = match (user) {
|
|
2036
|
+
null => "not found",
|
|
2037
|
+
_ => `Hello, ${user.name}`,
|
|
2038
|
+
};
|
|
2039
|
+
|
|
2040
|
+
// enum
|
|
2041
|
+
const desc = match (direction) {
|
|
2042
|
+
Direction.North => "вверх",
|
|
2043
|
+
Direction.South => "вниз",
|
|
2044
|
+
Direction.East => "вправо",
|
|
2045
|
+
Direction.West => "влево",
|
|
2046
|
+
// _ не нужен — компилятор проверяет полноту
|
|
2047
|
+
};
|
|
2048
|
+
|
|
2049
|
+
// деструктуризация struct / class
|
|
2050
|
+
const area = match (shape) {
|
|
2051
|
+
{ kind: "circle", r } => Math.PI * r * r,
|
|
2052
|
+
{ kind: "rect", w, h } => w * h,
|
|
2053
|
+
};
|
|
2054
|
+
|
|
2055
|
+
// несколько паттернов для одной ветки
|
|
2056
|
+
const sign = match (n) {
|
|
2057
|
+
0 => "zero",
|
|
2058
|
+
1 | 2 | 3 => "small positive",
|
|
2059
|
+
_ => "other",
|
|
2060
|
+
};
|
|
2061
|
+
```
|
|
2062
|
+
|
|
2063
|
+
**Правила match:**
|
|
2064
|
+
|
|
2065
|
+
- `_` — wildcard, совпадает с чем угодно; обязателен если паттерны не исчерпывающие
|
|
2066
|
+
- Паттерны проверяются сверху вниз, срабатывает первый совпавший
|
|
2067
|
+
- Exhaustiveness: если компилятор видит что все случаи покрыты (enum, null + non-null) — `_` не нужен; если не покрыты — ошибка компилятора
|
|
2068
|
+
- `|` — несколько паттернов для одной ветки
|
|
2069
|
+
- Диапазон `a..b` — от `a` включительно до `b` не включительно (как везде в TSC)
|
|
2070
|
+
- Деструктуризация в паттерне — те же правила borrow что и в обычной деструктуризации (только borrow)
|
|
2071
|
+
|
|
2072
|
+
**match vs switch:**
|
|
2073
|
+
|
|
2074
|
+
| | `switch` | `match` |
|
|
2075
|
+
|---|---|---|
|
|
2076
|
+
| Тип | statement | expression (возвращает значение) |
|
|
2077
|
+
| Exhaustiveness | warning | ошибка компилятора |
|
|
2078
|
+
| Паттерны | только равенство | литералы, диапазоны, деструктуризация, `\|` |
|
|
2079
|
+
| Fallthrough | запрещён | нет (каждая ветка — отдельное выражение) |
|
|
2080
|
+
|
|
2081
|
+
#### Доступ к полям и деструктуризация
|
|
2082
|
+
|
|
2083
|
+
##### Доступ к полю — borrow по умолчанию
|
|
2084
|
+
|
|
2085
|
+
Обращение к полю сложного типа без аннотации возвращает `Ref`:
|
|
2086
|
+
|
|
2087
|
+
```typescript
|
|
2088
|
+
const user = new User("Alice", [1, 2, 3]);
|
|
2089
|
+
|
|
2090
|
+
const name = user.name; // Ref<string> — borrow, user жив
|
|
2091
|
+
const age = user.age; // i32 — copy (примитив)
|
|
2092
|
+
|
|
2093
|
+
console.log(user); // ok — user не тронут
|
|
2094
|
+
console.log(user.name); // ok
|
|
2095
|
+
```
|
|
2096
|
+
|
|
2097
|
+
Чтобы переместить поле — явная аннотация типа владельца:
|
|
2098
|
+
|
|
2099
|
+
```typescript
|
|
2100
|
+
const name: string = user.name; // string (T) — move
|
|
2101
|
+
console.log(user.name); // ошибка: поле перемещено
|
|
2102
|
+
console.log(user.age); // ok — остальные поля живы
|
|
2103
|
+
console.log(user); // ошибка: нельзя использовать user целиком после move поля
|
|
2104
|
+
```
|
|
2105
|
+
|
|
2106
|
+
##### Деструктуризация — сахар для borrow-доступа к полям
|
|
2107
|
+
|
|
2108
|
+
Деструктуризация **всегда** делает borrow для сложных типов и copy для примитивов. Move через деструктуризацию невозможен.
|
|
2109
|
+
|
|
2110
|
+
```typescript
|
|
2111
|
+
const { name, age } = user;
|
|
2112
|
+
// эквивалентно:
|
|
2113
|
+
// const name = user.name; → Ref<string> (borrow, не move)
|
|
2114
|
+
// const age = user.age; → i32 (copy)
|
|
2115
|
+
```
|
|
2116
|
+
|
|
2117
|
+
`user` остаётся жив после деструктуризации:
|
|
2118
|
+
|
|
2119
|
+
```typescript
|
|
2120
|
+
const user = new User("Alice", 30, [1, 2, 3]);
|
|
2121
|
+
const { name, age, scores } = user;
|
|
2122
|
+
// name: Ref<string>, age: i32, scores: Ref<i32[]>
|
|
2123
|
+
|
|
2124
|
+
console.log(user); // ok — ничего не перемещено
|
|
2125
|
+
console.log(name); // ok
|
|
2126
|
+
console.log(scores); // ok
|
|
2127
|
+
```
|
|
2128
|
+
|
|
2129
|
+
Для move — только явное присвоение с аннотацией типа (не деструктуризация):
|
|
2130
|
+
|
|
2131
|
+
```typescript
|
|
2132
|
+
const scores: i32[] = user.scores; // move — явная аннотация
|
|
2133
|
+
const name = user.name; // Ref<string> — borrow
|
|
2134
|
+
console.log(user.scores); // ошибка: поле перемещено
|
|
2135
|
+
console.log(user.name); // ok
|
|
2136
|
+
```
|
|
2137
|
+
|
|
2138
|
+
#### Срезы
|
|
2139
|
+
|
|
2140
|
+
По умолчанию срез — borrow (`Ref`), исходный массив остаётся жив. Явная аннотация типа даёт owned копию:
|
|
2141
|
+
|
|
2142
|
+
```typescript
|
|
2143
|
+
const arr = [1, 2, 3, 4, 5];
|
|
2144
|
+
|
|
2145
|
+
const s = arr[1..3]; // Ref<i32[]> — borrow, arr жив
|
|
2146
|
+
const s: i32[] = arr[1..3]; // i32[] — owned копия [2, 3]
|
|
2147
|
+
```
|
|
2148
|
+
|
|
2149
|
+
Borrow-срез блокирует мутацию источника пока жив:
|
|
2150
|
+
|
|
2151
|
+
```typescript
|
|
2152
|
+
let arr = [1, 2, 3, 4, 5];
|
|
2153
|
+
const s = arr[1..3]; // Ref — arr заимствован
|
|
2154
|
+
arr.push(6); // ошибка: arr заимствован
|
|
2155
|
+
```
|
|
2156
|
+
|
|
2157
|
+
Отрицательные индексы и открытые срезы:
|
|
2158
|
+
|
|
2159
|
+
```typescript
|
|
2160
|
+
const last = arr[-1]; // последний элемент (copy — примитив)
|
|
2161
|
+
const tail = arr[1..]; // Ref<i32[]> — с 1 до конца
|
|
2162
|
+
const init = arr[..-1]; // Ref<i32[]> — всё кроме последнего
|
|
2163
|
+
const last2 = arr[-2..]; // Ref<i32[]> — последние два
|
|
2164
|
+
```
|
|
2165
|
+
|
|
2166
|
+
#### Move из массива по индексу
|
|
2167
|
+
|
|
2168
|
+
```typescript
|
|
2169
|
+
let ref: User;
|
|
2170
|
+
{
|
|
2171
|
+
const users = [user1, user2, user3];
|
|
2172
|
+
ref = users[0]; // попытка move из массива
|
|
2173
|
+
} // users умирает → ref dangling
|
|
2174
|
+
```
|
|
2175
|
+
|
|
2176
|
+
```
|
|
2177
|
+
error: cannot move out of array by index
|
|
2178
|
+
--> main.tsc:4
|
|
2179
|
+
hint: use users.remove(0) to take ownership
|
|
2180
|
+
```
|
|
2181
|
+
|
|
2182
|
+
Исправление:
|
|
2183
|
+
|
|
2184
|
+
```typescript
|
|
2185
|
+
let ref: User;
|
|
2186
|
+
{
|
|
2187
|
+
let users = [user1, user2, user3];
|
|
2188
|
+
ref = users.remove(0); // move с удалением — ok
|
|
2189
|
+
}
|
|
2190
|
+
```
|
|
2191
|
+
|
|
2192
|
+
#### Мутация коллекции при активном borrow
|
|
2193
|
+
|
|
2194
|
+
Borrow элемента = borrow коллекции.
|
|
2195
|
+
|
|
2196
|
+
```typescript
|
|
2197
|
+
let users = [user1, user2, user3];
|
|
2198
|
+
let u: Ref<User> = users[0]; // borrow на users
|
|
2199
|
+
users.push(user4); // ошибка: mut на заимствованном
|
|
2200
|
+
```
|
|
2201
|
+
|
|
2202
|
+
#### Возврат borrow из метода
|
|
2203
|
+
|
|
2204
|
+
Возвращаемый `Ref<T>`/`Mut<T>` неявно привязан к `this`:
|
|
2205
|
+
|
|
2206
|
+
```typescript
|
|
2207
|
+
class Config {
|
|
2208
|
+
data: string[];
|
|
2209
|
+
|
|
2210
|
+
getFirst(): Ref<string> {
|
|
2211
|
+
return this.data[0]; // привязан к this
|
|
2212
|
+
}
|
|
2213
|
+
}
|
|
2214
|
+
|
|
2215
|
+
const config = new Config();
|
|
2216
|
+
const s = config.getFirst(); // ok — s привязан к config
|
|
2217
|
+
console.log(s); // ok
|
|
2218
|
+
```
|
|
2219
|
+
|
|
2220
|
+
```typescript
|
|
2221
|
+
let s: Ref<string>;
|
|
2222
|
+
{
|
|
2223
|
+
const config = new Config();
|
|
2224
|
+
s = config.getFirst(); // borrow привязан к config
|
|
2225
|
+
} // config умер
|
|
2226
|
+
console.log(s); // ошибка: config умер, s dangling
|
|
2227
|
+
```
|
|
2228
|
+
|
|
2229
|
+
#### Borrows в полях класса — запрещено
|
|
2230
|
+
|
|
2231
|
+
```typescript
|
|
2232
|
+
class View {
|
|
2233
|
+
data: Ref<User[]>; // ошибка: нельзя хранить borrow в поле
|
|
2234
|
+
}
|
|
2235
|
+
```
|
|
2236
|
+
|
|
2237
|
+
Альтернативы:
|
|
2238
|
+
|
|
2239
|
+
```typescript
|
|
2240
|
+
// Владеем данными
|
|
2241
|
+
class View {
|
|
2242
|
+
data: User[]; // owned
|
|
2243
|
+
}
|
|
2244
|
+
|
|
2245
|
+
// Или Shared
|
|
2246
|
+
class View {
|
|
2247
|
+
data: Shared<User[]>; // ARC
|
|
2248
|
+
}
|
|
2249
|
+
|
|
2250
|
+
// Временный доступ — через параметр метода
|
|
2251
|
+
function renderView(data: Ref<User[]>) { ... }
|
|
2252
|
+
```
|
|
2253
|
+
|
|
2254
|
+
#### Замыкания
|
|
2255
|
+
|
|
2256
|
+
По умолчанию захватывают сложные типы по `Ref`:
|
|
2257
|
+
|
|
2258
|
+
```typescript
|
|
2259
|
+
const items = [1, 2, 3];
|
|
2260
|
+
const fn = (): i32 => items.length; // fn держит Ref<items>
|
|
2261
|
+
fn(); // ok — items жив
|
|
2262
|
+
```
|
|
2263
|
+
|
|
2264
|
+
```typescript
|
|
2265
|
+
let fn: () => i32;
|
|
2266
|
+
{
|
|
2267
|
+
const items = [1, 2, 3];
|
|
2268
|
+
fn = (): i32 => items.length; // захватывает Ref<items>
|
|
2269
|
+
}
|
|
2270
|
+
fn(); // ошибка: items мёртв
|
|
2271
|
+
```
|
|
2272
|
+
|
|
2273
|
+
Для явного управления захватом используется список `[var: Type]` перед параметрами — те же типы что везде:
|
|
2274
|
+
|
|
2275
|
+
```typescript
|
|
2276
|
+
[data: Data]() // T — move, замыкание становится владельцем
|
|
2277
|
+
[data: Ref<Data>]() // Ref — immutable borrow (явно, то же что по умолчанию)
|
|
2278
|
+
[data: Mut<Data>]() // Mut — mutable borrow
|
|
2279
|
+
```
|
|
2280
|
+
|
|
2281
|
+
Move-захват решает проблему когда замыкание переживает источник:
|
|
2282
|
+
|
|
2283
|
+
```typescript
|
|
2284
|
+
// ошибка без явного захвата — Ref не может пережить функцию
|
|
2285
|
+
function makeGreeter(): () => void {
|
|
2286
|
+
const name = "Alice";
|
|
2287
|
+
return (): void => console.log(name); // ошибка: name умрёт
|
|
2288
|
+
}
|
|
2289
|
+
|
|
2290
|
+
// ok — name перемещён в замыкание, живёт пока живёт замыкание
|
|
2291
|
+
function makeGreeter(): () => void {
|
|
2292
|
+
const name = "Alice";
|
|
2293
|
+
return [name: string](): void => console.log(name); // ok
|
|
2294
|
+
}
|
|
2295
|
+
```
|
|
2296
|
+
|
|
2297
|
+
```typescript
|
|
2298
|
+
// Mut-захват — замыкание мутирует внешний объект
|
|
2299
|
+
let counter = new Counter();
|
|
2300
|
+
const inc = [counter: Mut<Counter>]() => counter.increment();
|
|
2301
|
+
inc();
|
|
2302
|
+
inc();
|
|
2303
|
+
```
|
|
2304
|
+
|
|
2305
|
+
#### Spread оператор
|
|
2306
|
+
|
|
2307
|
+
Spread **потребляет** источник — move. Работает для массивов и объектов. Spread на `const` — ошибка компилятора. **Исключение**: `const Shared<T>` — разрешено, это retain, не move.
|
|
2308
|
+
|
|
2309
|
+
**Массивы:**
|
|
2310
|
+
```typescript
|
|
2311
|
+
let admins = [admin1, admin2];
|
|
2312
|
+
const users = [...admins, ...guests]; // ok — move из let
|
|
2313
|
+
sendEmail(admins); // ошибка: admins перемещён
|
|
2314
|
+
|
|
2315
|
+
const admins = [admin1, admin2];
|
|
2316
|
+
const users = [...admins];
|
|
2317
|
+
// ошибка: cannot spread const
|
|
2318
|
+
// hint: use let, Shared<T>, or [...admins.clone()] if Admin implements Clone
|
|
2319
|
+
```
|
|
2320
|
+
|
|
2321
|
+
**Объекты:**
|
|
2322
|
+
```typescript
|
|
2323
|
+
let base = { x: 1, name: "Alice" };
|
|
2324
|
+
const extended = { ...base, extra: 42 }; // ok — move из let
|
|
2325
|
+
console.log(base); // ошибка: base перемещён
|
|
2326
|
+
|
|
2327
|
+
const base = { x: 1, name: "Alice" };
|
|
2328
|
+
const extended = { ...base, extra: 42 };
|
|
2329
|
+
// ошибка: cannot spread const
|
|
2330
|
+
// hint: use let, Shared<T>, or { ...base.clone(), extra: 42 } if type implements Clone
|
|
2331
|
+
```
|
|
2332
|
+
|
|
2333
|
+
**`Shared<T>` — const разрешён** (retain, не move):
|
|
2334
|
+
|
|
2335
|
+
```typescript
|
|
2336
|
+
const base: Shared<Item[]> = [item1, item2];
|
|
2337
|
+
const listA = [...base, itemA]; // ok — retain
|
|
2338
|
+
const listB = [...base, itemB]; // ok — retain
|
|
2339
|
+
|
|
2340
|
+
const obj: Shared<Config> = { x: 1 };
|
|
2341
|
+
const a = { ...obj, y: 2 }; // ok — retain
|
|
2342
|
+
const b = { ...obj, z: 3 }; // ok — retain
|
|
2343
|
+
```
|
|
2344
|
+
|
|
2345
|
+
### Compiler Architecture
|
|
2346
|
+
|
|
2347
|
+
#### Фазы компиляции
|
|
2348
|
+
|
|
2349
|
+
```
|
|
2350
|
+
Parse → AST → Typecheck → Lower to IR → Ownership Analysis → Codegen
|
|
2351
|
+
↑ ↑
|
|
2352
|
+
Flatten CFG Borrow checker / ARC injection
|
|
2353
|
+
```
|
|
2354
|
+
|
|
2355
|
+
#### IR (Intermediate Representation)
|
|
2356
|
+
|
|
2357
|
+
IR — linear представление между AST и C. Flattens вложенность, делает порядок выполнения явным.
|
|
2358
|
+
|
|
2359
|
+
**Операции:**
|
|
2360
|
+
|
|
2361
|
+
| Операция | Описание |
|
|
2362
|
+
|----------|----------|
|
|
2363
|
+
| `alloc x, value` | Создать переменную, владелец |
|
|
2364
|
+
| `borrow x, source, imm/mut` | Заимствовать (`Ref`/`Mut`) |
|
|
2365
|
+
| `retain x` | Увеличить refcount (`Shared`) |
|
|
2366
|
+
| `release x` | Уменьшить refcount |
|
|
2367
|
+
| `call fn, args` | Вызов функции |
|
|
2368
|
+
| `assign x, value` | Присвоение |
|
|
2369
|
+
| `drop x` | Конец жизни переменной |
|
|
2370
|
+
| `return value` | Возврат |
|
|
2371
|
+
| `branch cond, label1, label2` | Условный переход |
|
|
2372
|
+
| `jump label` | Безусловный переход |
|
|
2373
|
+
|
|
2374
|
+
**Пример трансформации:**
|
|
2375
|
+
|
|
2376
|
+
TypeScript:
|
|
2377
|
+
```typescript
|
|
2378
|
+
let users = [user1, user2, user3]
|
|
2379
|
+
const first = users[0]
|
|
2380
|
+
push(users, user4)
|
|
2381
|
+
```
|
|
2382
|
+
|
|
2383
|
+
IR:
|
|
2384
|
+
```
|
|
2385
|
+
alloc users, [user1, user2, user3]
|
|
2386
|
+
borrow first, users[0], imm // first = Ref<User>
|
|
2387
|
+
call push, [users, user4] // ← ошибка: users заимствован
|
|
2388
|
+
drop first
|
|
2389
|
+
drop users
|
|
2390
|
+
```
|
|
2391
|
+
|
|
2392
|
+
**Почему IR:**
|
|
2393
|
+
|
|
2394
|
+
- Явный порядок операций (не как в AST)
|
|
2395
|
+
- Простые проверки для borrow checker
|
|
2396
|
+
- Легко вставлять `retain`/`release` для `Shared<T>`
|
|
2397
|
+
- Почти 1:1 с C — кодоген тривиальный
|
|
2398
|
+
|
|
2399
|
+
### Module System
|
|
2400
|
+
|
|
2401
|
+
- Синтаксис как в TypeScript: именованные `export` / `import { } from ""`
|
|
2402
|
+
- Один файл = один модуль
|
|
2403
|
+
- `export default` — отсутствует, только именованные экспорты
|
|
2404
|
+
- **Циклические импорты разрешены** — компилятор автоматически генерирует forward declarations в C
|
|
2405
|
+
|
|
2406
|
+
#### Export
|
|
2407
|
+
|
|
2408
|
+
```typescript
|
|
2409
|
+
export class User { ... }
|
|
2410
|
+
export interface Drawable { ... }
|
|
2411
|
+
export type UserId = i32;
|
|
2412
|
+
export type Nullable<T> = T | null;
|
|
2413
|
+
export function helper(): void { ... }
|
|
2414
|
+
export const MAX: i32 = 100;
|
|
2415
|
+
|
|
2416
|
+
// реэкспорт
|
|
2417
|
+
export { User, helper } from "./user";
|
|
2418
|
+
```
|
|
2419
|
+
|
|
2420
|
+
#### Import
|
|
2421
|
+
|
|
2422
|
+
```typescript
|
|
2423
|
+
// обычный импорт — runtime, генерирует #include "user.h" в C
|
|
2424
|
+
import { User, createUser } from "./user";
|
|
2425
|
+
|
|
2426
|
+
// type-only импорт — только compile-time, генерирует forward declaration в C
|
|
2427
|
+
import type { UserId, Drawable } from "./user";
|
|
2428
|
+
```
|
|
2429
|
+
|
|
2430
|
+
`import type` важен для кодогена — позволяет избежать лишних `#include` в C:
|
|
2431
|
+
```c
|
|
2432
|
+
// import { User } → в .c файле:
|
|
2433
|
+
#include "user.h"
|
|
2434
|
+
|
|
2435
|
+
// import type { UserId } → в .h файле:
|
|
2436
|
+
typedef int32_t UserId; // или forward declaration
|
|
2437
|
+
```
|
|
2438
|
+
|
|
2439
|
+
#### Порядок инициализации модулей
|
|
2440
|
+
|
|
2441
|
+
Каждый модуль с module-level переменными получает `_init()` функцию в C. Порядок вызовов определяется **топологической сортировкой** графа импортов — зависимости инициализируются раньше.
|
|
2442
|
+
|
|
2443
|
+
Для правильного порядка компилятор строит граф зависимостей и делает топологическую сортировку. Результат — одна функция `tsc_init_all()` с правильным порядком:
|
|
2444
|
+
|
|
2445
|
+
```c
|
|
2446
|
+
// сгенерировано компилятором
|
|
2447
|
+
static void tsc_init_all() {
|
|
2448
|
+
a_type_init(); // нет зависимостей — первый
|
|
2449
|
+
bar_init(); // зависит от a_type
|
|
2450
|
+
foo_init(); // зависит от a_type и bar
|
|
2451
|
+
}
|
|
2452
|
+
|
|
2453
|
+
int main() {
|
|
2454
|
+
tsc_init_all();
|
|
2455
|
+
// ... код пользователя
|
|
2456
|
+
}
|
|
2457
|
+
```
|
|
2458
|
+
|
|
2459
|
+
Два случая циклических зависимостей:
|
|
2460
|
+
|
|
2461
|
+
- **Цикл через типы и функции** — разрешён, компилятор генерирует forward declarations в .h файлах
|
|
2462
|
+
- **Цикл через module-level переменные** — физически неразрешимо, ошибка компилятора:
|
|
2463
|
+
```
|
|
2464
|
+
error: circular initialization dependency detected
|
|
2465
|
+
src/a.tsc:2 aValue depends on bValue
|
|
2466
|
+
src/b.tsc:2 bValue depends on aValue
|
|
2467
|
+
hint: move one of these values into a function
|
|
2468
|
+
```
|
|
2469
|
+
Пример в коде:
|
|
2470
|
+
```typescript
|
|
2471
|
+
a.tsc: const aVal = bFunc() // нужен b
|
|
2472
|
+
b.tsc: const bVal = aFunc() // нужен a
|
|
2473
|
+
// кто инициализируется первым?
|
|
2474
|
+
```
|
|
2475
|
+
|
|
2476
|
+
#### Точка входа
|
|
2477
|
+
|
|
2478
|
+
Поле `"main"` в `tsc.packages.json` указывает главный файл проекта. Компилятор определяет тип проекта по содержимому файла:
|
|
2479
|
+
|
|
2480
|
+
- Есть top-level statements → **executable**, компилятор генерирует `main()` в C
|
|
2481
|
+
- Только `export` декларации → **library**, `main()` не генерируется
|
|
2482
|
+
|
|
2483
|
+
```json
|
|
2484
|
+
{
|
|
2485
|
+
"name": "myapp",
|
|
2486
|
+
"main": "src/index.tsc"
|
|
2487
|
+
}
|
|
2488
|
+
```
|
|
2489
|
+
|
|
2490
|
+
```json
|
|
2491
|
+
{
|
|
2492
|
+
"name": "myapp",
|
|
2493
|
+
"builds": [
|
|
2494
|
+
{ "name": "server", "main": "src/server.tsc" },
|
|
2495
|
+
{ "name": "cli", "main": "src/cli.tsc" }
|
|
2496
|
+
]
|
|
2497
|
+
}
|
|
2498
|
+
```
|
|
2499
|
+
|
|
2500
|
+
`"main"` внутри build config переопределяет верхний уровень для конкретного билда.
|
|
2501
|
+
|
|
2502
|
+
**Без `tsc.packages.json`** (одиночный скрипт):
|
|
2503
|
+
- Один файл с top-level кодом → он entry point: `tsclang build hello.tsc`
|
|
2504
|
+
- Несколько файлов с top-level кодом → ошибка компилятора
|
|
2505
|
+
|
|
2506
|
+
**Правила:**
|
|
2507
|
+
|
|
2508
|
+
| Ситуация | Результат |
|
|
2509
|
+
|---------|-----------|
|
|
2510
|
+
| `"main"` + top-level код в файле | executable |
|
|
2511
|
+
| `"main"` + только exports | library |
|
|
2512
|
+
| Нет конфига + один файл с top-level кодом | он entry point |
|
|
2513
|
+
| Нет конфига + несколько файлов с top-level кодом | ошибка компилятора |
|
|
2514
|
+
|
|
2515
|
+
**Ошибки:**
|
|
2516
|
+
|
|
2517
|
+
- `"main"` не указан, несколько файлов с top-level кодом:
|
|
2518
|
+
```
|
|
2519
|
+
error: multiple files with top-level statements, entry point is ambiguous
|
|
2520
|
+
hint: add "main" to tsc.packages.json
|
|
2521
|
+
```
|
|
2522
|
+
|
|
2523
|
+
- `"main"` указан, файл не существует:
|
|
2524
|
+
```
|
|
2525
|
+
error: main file not found: src/index.tsc
|
|
2526
|
+
```
|
|
2527
|
+
|
|
2528
|
+
- Типы импортов по источнику:
|
|
2529
|
+
- `"./path"` — локальный файл
|
|
2530
|
+
- `"libc"`, `"libm"` и др. — встроенные декларации + генерирует `#include <...>` в C
|
|
2531
|
+
```typescript
|
|
2532
|
+
import { printf } from "libc";
|
|
2533
|
+
// компилятор знает сигнатуру printf — есть встроенный libc.d.tsc
|
|
2534
|
+
// генерирует в C: #include <stdio.h>
|
|
2535
|
+
```
|
|
2536
|
+
- остальное — внешние пакеты из реестра
|
|
2537
|
+
- **Файлы деклараций `.d.tsc`** — типизация внешнего кода:
|
|
2538
|
+
- Для C-библиотек без встроенных деклараций
|
|
2539
|
+
- Для `.tsc` модулей без исходников (бинарные пакеты)
|
|
2540
|
+
- Сообщество публикует `.d.tsc` для популярных C-либ в реестре
|
|
2541
|
+
- **Если деклараций нет** — тип `any`, компилятор не ругается
|
|
2542
|
+
|
|
2543
|
+
### Build System & Package Manager
|
|
2544
|
+
|
|
2545
|
+
#### Build Profiles
|
|
2546
|
+
|
|
2547
|
+
Именованные профили сборки в `tsc.packages.json`:
|
|
2548
|
+
|
|
2549
|
+
```json
|
|
2550
|
+
{
|
|
2551
|
+
"builds": {
|
|
2552
|
+
"desktop": {},
|
|
2553
|
+
"avr": {
|
|
2554
|
+
"target": "avr",
|
|
2555
|
+
"mcu": "atmega328p",
|
|
2556
|
+
"defaultNumber": "f32"
|
|
2557
|
+
},
|
|
2558
|
+
"release": {
|
|
2559
|
+
"optimize": "O2"
|
|
2560
|
+
}
|
|
2561
|
+
}
|
|
2562
|
+
}
|
|
2563
|
+
```
|
|
2564
|
+
|
|
2565
|
+
#### Поля build конфига
|
|
2566
|
+
|
|
2567
|
+
| Поле | Описание | Дефолт |
|
|
2568
|
+
|------|----------|--------|
|
|
2569
|
+
| `"name"` | имя билда | обязательно |
|
|
2570
|
+
| `"main"` | entry point файл (override верхнего уровня) | наследует |
|
|
2571
|
+
| `"emit"` | тип вывода: `"c"`, `"binary"`, `"hex"`, `"lib"` | `"binary"` для desktop, `"hex"` для embedded |
|
|
2572
|
+
| `"outDir"` | директория вывода | `./build/<name>` |
|
|
2573
|
+
| `"target"` | целевая платформа | текущая платформа |
|
|
2574
|
+
| `"mcu"` | модель MCU (только для embedded) | — |
|
|
2575
|
+
| `"optimize"` | уровень оптимизации (`O0`..`O3`, `Os`) | `O0` |
|
|
2576
|
+
| `"defaultNumber"` | тип для `number` | `f64` |
|
|
2577
|
+
|
|
2578
|
+
#### Pipeline сборки
|
|
2579
|
+
|
|
2580
|
+
```
|
|
2581
|
+
src/*.tsc → <outDir>/c/*.c + CMakeLists.txt → <outDir>/myapp (или .hex)
|
|
2582
|
+
↑ ↑
|
|
2583
|
+
tsclang build (transpile) cmake + gcc/avr-gcc
|
|
2584
|
+
```
|
|
2585
|
+
|
|
2586
|
+
Структура `outDir`:
|
|
2587
|
+
```
|
|
2588
|
+
build/desktop/
|
|
2589
|
+
c/ ← сгенерированные .c и .h
|
|
2590
|
+
CMakeLists.txt
|
|
2591
|
+
myapp ← бинарь (emit: binary)
|
|
2592
|
+
|
|
2593
|
+
build/avr/
|
|
2594
|
+
c/
|
|
2595
|
+
CMakeLists.txt
|
|
2596
|
+
myapp.hex ← (emit: hex)
|
|
2597
|
+
```
|
|
2598
|
+
|
|
2599
|
+
#### CLI команды
|
|
2600
|
+
|
|
2601
|
+
```bash
|
|
2602
|
+
tsclang init # создать новый проект
|
|
2603
|
+
tsclang build # собрать проект
|
|
2604
|
+
tsclang install # установить зависимости из tsc.packages.json
|
|
2605
|
+
tsclang update # обновить зависимости, пересоздать lock-файл
|
|
2606
|
+
tsclang clean # удалить build артефакты (outDir)
|
|
2607
|
+
tsclang run # собрать дефолтный build + запустить бинарь
|
|
2608
|
+
```
|
|
2609
|
+
|
|
2610
|
+
- Если build не указан — используется `"desktop"` или первый в списке
|
|
2611
|
+
- Параметры build переопределяют дефолтные настройки компилятора
|
|
2612
|
+
|
|
2613
|
+
#### `tsclang install` vs `tsclang update`
|
|
2614
|
+
|
|
2615
|
+
| | `tsclang install` | `tsclang update` |
|
|
2616
|
+
|---|---|---|
|
|
2617
|
+
| Lock-файл существует | использует точные версии из lock | игнорирует lock, ищет новые версии |
|
|
2618
|
+
| Lock-файл отсутствует | резолвит по constraints, создаёт lock | то же |
|
|
2619
|
+
| Результат | воспроизводимая установка | обновлённый lock-файл |
|
|
2620
|
+
|
|
2621
|
+
#### `tsclang update` подробно
|
|
2622
|
+
|
|
2623
|
+
Поведение по типу зависимости:
|
|
2624
|
+
|
|
2625
|
+
| Тип | Поведение |
|
|
2626
|
+
|-----|-----------|
|
|
2627
|
+
| semver `^1.0.0` | обновляет до последней версии в рамках constraint |
|
|
2628
|
+
| git `@main` (ветка) | pull latest commit, обновляет lock |
|
|
2629
|
+
| git `@1.0.0` (тег) | зафиксирован — пропускает, выводит предупреждение |
|
|
2630
|
+
| git `@a1b2c3d` (коммит) | зафиксирован — пропускает, выводит предупреждение |
|
|
2631
|
+
| url | нет реестра — пропускает, выводит предупреждение |
|
|
2632
|
+
|
|
2633
|
+
```bash
|
|
2634
|
+
tsclang update # обновить всё что можно
|
|
2635
|
+
tsclang update <dep> # обновить конкретную зависимость
|
|
2636
|
+
tsclang update sdl2 # обновить только sdl2
|
|
2637
|
+
tsclang update sdl2 json # обновить несколько
|
|
2638
|
+
```
|
|
2639
|
+
|
|
2640
|
+
После `tsclang update` необходимо запустить `tsclang install` для применения изменений.
|
|
2641
|
+
|
|
2642
|
+
#### `tsclang build` подробно
|
|
2643
|
+
|
|
2644
|
+
```bash
|
|
2645
|
+
tsclang build # собрать дефолтный build
|
|
2646
|
+
tsclang build <name> # собрать конкретный build
|
|
2647
|
+
tsclang build hello.tsc # одиночный файл → binary
|
|
2648
|
+
|
|
2649
|
+
# флаги (override конфига)
|
|
2650
|
+
tsclang build --emit c # только генерация C
|
|
2651
|
+
tsclang build --emit binary # C + компиляция в бинарь
|
|
2652
|
+
tsclang build --emit hex # C + avr-gcc → .hex
|
|
2653
|
+
tsclang build --outDir ./dist # переопределить outDir
|
|
2654
|
+
```
|
|
2655
|
+
|
|
2656
|
+
- Если build не указан — используется `"desktop"` или первый в списке
|
|
2657
|
+
- Параметры build переопределяют дефолтные настройки компилятора
|
|
2658
|
+
|
|
2659
|
+
#### `tsclang run` подробно
|
|
2660
|
+
|
|
2661
|
+
```bash
|
|
2662
|
+
tsclang run # собрать дефолтный build + запустить бинарь
|
|
2663
|
+
tsclang run <name> # собрать конкретный build + запустить бинарь
|
|
2664
|
+
tsclang run -- --foo bar # передать аргументы в запускаемый бинарь
|
|
2665
|
+
```
|
|
2666
|
+
|
|
2667
|
+
`tsclang run` = `tsclang build` + запуск скомпилированного бинаря. Только для `emit: "binary"`.
|
|
2668
|
+
|
|
2669
|
+
```
|
|
2670
|
+
tsclang run
|
|
2671
|
+
│
|
|
2672
|
+
├─ 1. tsclang build ← компилирует .tsc → .c → бинарь
|
|
2673
|
+
└─ 2. exec <outDir>/myapp ← запускает бинарь, stdout/stderr в терминал
|
|
2674
|
+
```
|
|
2675
|
+
|
|
2676
|
+
- Если `emit` не `"binary"` — ошибка: `error: tsclang run requires emit: "binary"`
|
|
2677
|
+
- Код выхода бинаря пробрасывается как код выхода `tsclang run`
|
|
2678
|
+
- Аргументы после `--` передаются напрямую в бинарь:
|
|
2679
|
+
```bash
|
|
2680
|
+
tsclang run -- --port 8080 --verbose
|
|
2681
|
+
# запускает: ./build/desktop/myapp --port 8080 --verbose
|
|
2682
|
+
```
|
|
2683
|
+
|
|
2684
|
+
#### `tsclang init` подробно
|
|
2685
|
+
|
|
2686
|
+
```bash
|
|
2687
|
+
tsclang init # создать проект в текущей директории
|
|
2688
|
+
tsclang init myapp # создать проект в новой директории myapp
|
|
2689
|
+
```
|
|
2690
|
+
|
|
2691
|
+
`tsclang init` создаёт:
|
|
2692
|
+
|
|
2693
|
+
```
|
|
2694
|
+
myapp/
|
|
2695
|
+
src/
|
|
2696
|
+
index.tsc
|
|
2697
|
+
tsc.packages.json
|
|
2698
|
+
```
|
|
2699
|
+
|
|
2700
|
+
Минимальный `tsc.packages.json`:
|
|
2701
|
+
|
|
2702
|
+
```json
|
|
2703
|
+
{
|
|
2704
|
+
"name": "myapp",
|
|
2705
|
+
"version": "1.0.0",
|
|
2706
|
+
"main": "src/index.tsc",
|
|
2707
|
+
"builds": [
|
|
2708
|
+
{ "name": "desktop", "emit": "binary", "outDir": "build/desktop" }
|
|
2709
|
+
]
|
|
2710
|
+
}
|
|
2711
|
+
```
|
|
2712
|
+
|
|
2713
|
+
#### Быстрый старт
|
|
2714
|
+
|
|
2715
|
+
```bash
|
|
2716
|
+
npm install -g tsclang # установить компилятор
|
|
2717
|
+
tsclang init myapp # создать проект
|
|
2718
|
+
cd myapp
|
|
2719
|
+
tsclang install # установить зависимости
|
|
2720
|
+
tsclang run # собрать и запустить
|
|
2721
|
+
```
|
|
2722
|
+
|
|
2723
|
+
#### Источники зависимостей (все варианты вместе)
|
|
2724
|
+
|
|
2725
|
+
```json
|
|
2726
|
+
{
|
|
2727
|
+
"dependencies": {
|
|
2728
|
+
"mylib": "^1.0.0",
|
|
2729
|
+
"sdl2": ">=2.28.0",
|
|
2730
|
+
"json": {
|
|
2731
|
+
"git": "github.com/nlohmann/json@3.11.0"
|
|
2732
|
+
},
|
|
2733
|
+
"libfoo": {
|
|
2734
|
+
"git": "github.com/someuser/libfoo@1.0.0",
|
|
2735
|
+
"build": "make PREFIX={install_dir}",
|
|
2736
|
+
"headers": "include/",
|
|
2737
|
+
"lib": "libfoo.a"
|
|
2738
|
+
},
|
|
2739
|
+
"libbaz": {
|
|
2740
|
+
"url": "https://some.site.com/download/lib_1.0.0.zip",
|
|
2741
|
+
"version": "1.0.0",
|
|
2742
|
+
"build": "make PREFIX={install_dir}",
|
|
2743
|
+
"headers": "include/",
|
|
2744
|
+
"lib": "libbaz.a"
|
|
2745
|
+
}
|
|
2746
|
+
}
|
|
2747
|
+
}
|
|
2748
|
+
```
|
|
2749
|
+
|
|
2750
|
+
#### Версионирование
|
|
2751
|
+
|
|
2752
|
+
- **Semver строка** — полный semver: `^1.0.0`, `~1.2.0`, `>=1.0.0`, `1.0.0`
|
|
2753
|
+
- **Git** — только точный тег (`@2.28.0`), коммит (`@a1b2c3d`), или ветка (`@main`); semver операторы не поддерживаются
|
|
2754
|
+
- **URL** — версия задаётся обязательным полем `version:` (используется для кэша и lock-файла)
|
|
2755
|
+
|
|
2756
|
+
#### Резолюция semver-зависимостей
|
|
2757
|
+
|
|
2758
|
+
Для зависимостей заданных строкой компилятор ищет в следующем порядке:
|
|
2759
|
+
|
|
2760
|
+
1. **Система** — `pkg-config` проверяет наличие и версию
|
|
2761
|
+
- Найдена и версия удовлетворяет constraint → используем, ничего не скачиваем
|
|
2762
|
+
- Не найдена или версия не подходит → переходим к шагу 2
|
|
2763
|
+
2. **Реестр** (`tsc-lang.org`) — скачивает и собирает нужную версию
|
|
2764
|
+
- _(реестр не реализован)_ → ошибка компилятора с подсказкой:
|
|
2765
|
+
```
|
|
2766
|
+
error: sdl2 >=2.28.0 not found
|
|
2767
|
+
hint: install it manually, e.g.:
|
|
2768
|
+
apt install libsdl2-dev
|
|
2769
|
+
brew install sdl2
|
|
2770
|
+
```
|
|
2771
|
+
|
|
2772
|
+
#### URL-зависимости (zip-архив)
|
|
2773
|
+
|
|
2774
|
+
- Поле `url:` — прямая ссылка на `.zip` архив
|
|
2775
|
+
- Поле `version:` — **обязательно**, используется для именования кэша и lock-файла
|
|
2776
|
+
- Поддерживаемые форматы архивов: `.zip`, `.tar.gz`, `.tar.bz2`, `.tar.xz`
|
|
2777
|
+
- Flow:
|
|
2778
|
+
|
|
2779
|
+
```bash
|
|
2780
|
+
# 1. Скачивает архив
|
|
2781
|
+
curl -L https://some.site.com/download/lib_1.0.0.zip \
|
|
2782
|
+
-o ~/.tsc/cache/libbaz@1.0.0.zip
|
|
2783
|
+
|
|
2784
|
+
# 2. Распаковывает
|
|
2785
|
+
unzip ~/.tsc/cache/libbaz@1.0.0.zip -d ~/.tsc/cache/libbaz@1.0.0/
|
|
2786
|
+
```
|
|
2787
|
+
|
|
2788
|
+
- Дальше — тот же порядок инструкций что и для git:
|
|
2789
|
+
1. **CMake** — есть `CMakeLists.txt` → auto-flow
|
|
2790
|
+
2. **`tsc.build.json`** — есть в архиве → используем
|
|
2791
|
+
3. **inline в `tsc.packages.json`** — описываем сами
|
|
2792
|
+
4. Ничего → ошибка компилятора
|
|
2793
|
+
- В lock-файле фиксируется URL + `sha256` архива для воспроизводимости
|
|
2794
|
+
|
|
2795
|
+
#### Git-зависимости
|
|
2796
|
+
|
|
2797
|
+
- Версия по тегу (`@2.28.0`), ветке (`@main`) или коммиту (`@a1b2c3d4`)
|
|
2798
|
+
- Lock-файл `tsc.packages.lock` — фиксирует точные коммиты для воспроизводимости
|
|
2799
|
+
- Сборка скачанной либы — приоритет поиска инструкций:
|
|
2800
|
+
1. **CMake** — есть `CMakeLists.txt` в репо → поддерживается автоматически
|
|
2801
|
+
2. **`tsc.build.json`** — есть в репо библиотеки → используем его
|
|
2802
|
+
3. **inline в `tsc.packages.json`** — описываем сборку прямо в своём проекте
|
|
2803
|
+
4. Ничего из вышеперечисленного → ошибка компилятора
|
|
2804
|
+
- `tsc.build.json` в корне репо библиотеки (удобство для авторов либ, чтобы пользователи не описывали сборку вручную):
|
|
2805
|
+
```json
|
|
2806
|
+
{
|
|
2807
|
+
"build": "make PREFIX={install_dir}",
|
|
2808
|
+
"headers": "include/",
|
|
2809
|
+
"lib": "libfoo.a"
|
|
2810
|
+
}
|
|
2811
|
+
```
|
|
2812
|
+
|
|
2813
|
+
##### CMake auto-flow
|
|
2814
|
+
|
|
2815
|
+
Когда в репо есть `CMakeLists.txt`, компилятор запускает стандартный cmake pipeline:
|
|
2816
|
+
|
|
2817
|
+
```bash
|
|
2818
|
+
# 1. Клонирует репо в кэш
|
|
2819
|
+
git clone github.com/someuser/libfoo@1.0.0 ~/.tsc/cache/libfoo@1.0.0
|
|
2820
|
+
|
|
2821
|
+
# 2. Конфигурирует — cmake_options из tsc.packages.json пробрасываются как -D флаги
|
|
2822
|
+
cmake -S ~/.tsc/cache/libfoo@1.0.0 \
|
|
2823
|
+
-B ~/.tsc/cache/libfoo@1.0.0/_build \
|
|
2824
|
+
-DCMAKE_INSTALL_PREFIX=~/.tsc/cache/libfoo@1.0.0/_install \
|
|
2825
|
+
-DBUILD_SHARED_LIBS=OFF \
|
|
2826
|
+
-DCMAKE_BUILD_TYPE=Release \
|
|
2827
|
+
-DFOO_BUILD_TESTS=OFF \ # ← из cmake_options
|
|
2828
|
+
-DFOO_USE_SSL=ON # ← из cmake_options
|
|
2829
|
+
|
|
2830
|
+
# 3. Собирает
|
|
2831
|
+
cmake --build ~/.tsc/cache/libfoo@1.0.0/_build --parallel
|
|
2832
|
+
|
|
2833
|
+
# 4. Устанавливает в _install/
|
|
2834
|
+
cmake --install ~/.tsc/cache/libfoo@1.0.0/_build
|
|
2835
|
+
```
|
|
2836
|
+
|
|
2837
|
+
После install — стандартная структура:
|
|
2838
|
+
|
|
2839
|
+
```
|
|
2840
|
+
_install/
|
|
2841
|
+
include/ ← headers
|
|
2842
|
+
lib/ ← libfoo.a
|
|
2843
|
+
lib/cmake/ ← FooConfig.cmake (если есть)
|
|
2844
|
+
```
|
|
2845
|
+
|
|
2846
|
+
Линковка в генерируемый `CMakeLists.txt` проекта — два варианта:
|
|
2847
|
+
|
|
2848
|
+
```cmake
|
|
2849
|
+
# Вариант A: есть FooConfig.cmake / foo-config.cmake → используем find_package
|
|
2850
|
+
find_package(Foo REQUIRED
|
|
2851
|
+
PATHS ~/.tsc/cache/libfoo@1.0.0/_install
|
|
2852
|
+
NO_DEFAULT_PATH)
|
|
2853
|
+
target_link_libraries(myapp PRIVATE Foo::Foo)
|
|
2854
|
+
|
|
2855
|
+
# Вариант B: config-файла нет → прописываем пути напрямую
|
|
2856
|
+
target_include_directories(myapp PRIVATE ~/.tsc/cache/libfoo@1.0.0/_install/include)
|
|
2857
|
+
target_link_libraries(myapp PRIVATE ~/.tsc/cache/libfoo@1.0.0/_install/lib/libfoo.a)
|
|
2858
|
+
```
|
|
2859
|
+
|
|
2860
|
+
##### cmake_options в tsc.packages.json
|
|
2861
|
+
|
|
2862
|
+
Опциональное поле для передачи `-D` флагов при конфигурации:
|
|
2863
|
+
|
|
2864
|
+
```json
|
|
2865
|
+
{
|
|
2866
|
+
"dependencies": {
|
|
2867
|
+
"libfoo": {
|
|
2868
|
+
"git": "github.com/someuser/libfoo@1.0.0",
|
|
2869
|
+
"cmake_options": {
|
|
2870
|
+
"FOO_BUILD_TESTS": false,
|
|
2871
|
+
"FOO_USE_SSL": true,
|
|
2872
|
+
"FOO_MAX_CONNECTIONS": 128
|
|
2873
|
+
}
|
|
2874
|
+
}
|
|
2875
|
+
}
|
|
2876
|
+
}
|
|
2877
|
+
```
|
|
2878
|
+
|
|
2879
|
+
- `boolean` → `ON` / `OFF`
|
|
2880
|
+
- `number` / `string` → передаётся как есть
|
|
2881
|
+
- Компилятор всегда добавляет `BUILD_SHARED_LIBS=OFF`, `CMAKE_BUILD_TYPE=Release`, `CMAKE_INSTALL_PREFIX` — пользователь не переопределяет эти три
|
|
2882
|
+
|
|
2883
|
+
##### Flow сборки для tsc.build.json / inline
|
|
2884
|
+
|
|
2885
|
+
```bash
|
|
2886
|
+
# Запускает сборку, подставляет {install_dir}
|
|
2887
|
+
make PREFIX=~/.tsc/cache/libfoo@1.0.0/out
|
|
2888
|
+
# Забирает результат по путям из инструкций
|
|
2889
|
+
# headers: include/ → ~/.tsc/cache/libfoo@1.0.0/include/
|
|
2890
|
+
# lib: libfoo.a → ~/.tsc/cache/libfoo@1.0.0/libfoo.a
|
|
2891
|
+
# Прописывает пути в генерируемый CMakeLists.txt проекта
|
|
2892
|
+
target_include_directories(myapp PRIVATE ~/.tsc/cache/libfoo@1.0.0/include)
|
|
2893
|
+
target_link_libraries(myapp ~/.tsc/cache/libfoo@1.0.0/libfoo.a)
|
|
2894
|
+
```
|
|
2895
|
+
|
|
2896
|
+
#### Реестр
|
|
2897
|
+
|
|
2898
|
+
- Централизованный реестр `tsc-lang.org`
|
|
2899
|
+
- Публикация `.tsc` пакетов и `.d.tsc` деклараций для C-либ
|
|
2900
|
+
|
|
2901
|
+
### Error Handling
|
|
2902
|
+
|
|
2903
|
+
#### Принцип
|
|
2904
|
+
|
|
2905
|
+
Синтаксис как в TypeScript (`throw`, `try`/`catch`/`finally`), но под капотом компилируется в **Result-структуры в C** — без `setjmp`/`longjmp`. Это даёт:
|
|
2906
|
+
|
|
2907
|
+
- **Zero-cost**: нет сохранения регистров на каждом `try`-блоке
|
|
2908
|
+
- **Безопасный C interop**: нет `longjmp` через сторонний C-код
|
|
2909
|
+
- **Корректный ownership**: обычный control flow, компилятор знает все owned переменные
|
|
2910
|
+
|
|
2911
|
+
#### Объявление функции с ошибками
|
|
2912
|
+
|
|
2913
|
+
Функция объявляет `throws` в сигнатуре. Компилятор может вывести `throws` автоматически, если внутри есть `throw`, но явное объявление является документацией:
|
|
2914
|
+
|
|
2915
|
+
```typescript
|
|
2916
|
+
function readFile(path: string): string throws IOError { ... }
|
|
2917
|
+
function fetch(url: string): Response throws IOError | NetworkError { ... }
|
|
2918
|
+
```
|
|
2919
|
+
|
|
2920
|
+
Без `throws` — функция не может содержать `throw` (ошибка компилятора).
|
|
2921
|
+
|
|
2922
|
+
#### throw
|
|
2923
|
+
|
|
2924
|
+
Бросается экземпляр класса:
|
|
2925
|
+
|
|
2926
|
+
```typescript
|
|
2927
|
+
class IOError {
|
|
2928
|
+
message: string;
|
|
2929
|
+
constructor(msg: string) { this.message = msg; }
|
|
2930
|
+
}
|
|
2931
|
+
|
|
2932
|
+
function readFile(path: string): string throws IOError {
|
|
2933
|
+
if (!exists(path)) {
|
|
2934
|
+
throw new IOError(`file not found: ${path}`);
|
|
2935
|
+
}
|
|
2936
|
+
return read(path);
|
|
2937
|
+
}
|
|
2938
|
+
```
|
|
2939
|
+
|
|
2940
|
+
#### try / catch / finally
|
|
2941
|
+
|
|
2942
|
+
```typescript
|
|
2943
|
+
try {
|
|
2944
|
+
const content = readFile("data.txt");
|
|
2945
|
+
console.log(content);
|
|
2946
|
+
} catch (e: IOError) {
|
|
2947
|
+
console.log(e.message);
|
|
2948
|
+
} finally {
|
|
2949
|
+
cleanup(); // выполняется всегда
|
|
2950
|
+
}
|
|
2951
|
+
```
|
|
2952
|
+
|
|
2953
|
+
Несколько `catch`-блоков — диспатч по типу:
|
|
2954
|
+
|
|
2955
|
+
```typescript
|
|
2956
|
+
try {
|
|
2957
|
+
const r = fetch("https://...");
|
|
2958
|
+
process(r);
|
|
2959
|
+
} catch (e: IOError) {
|
|
2960
|
+
console.log("IO:", e.message);
|
|
2961
|
+
} catch (e: NetworkError) {
|
|
2962
|
+
console.log("Network:", e.message);
|
|
2963
|
+
} finally {
|
|
2964
|
+
closeConnection();
|
|
2965
|
+
}
|
|
2966
|
+
```
|
|
2967
|
+
|
|
2968
|
+
Union catch — обработка нескольких типов в одном блоке:
|
|
2969
|
+
|
|
2970
|
+
```typescript
|
|
2971
|
+
try {
|
|
2972
|
+
fetch("https://...");
|
|
2973
|
+
} catch (e: IOError | NetworkError) {
|
|
2974
|
+
console.log("error:", e.message); // тип e = IOError | NetworkError
|
|
2975
|
+
}
|
|
2976
|
+
```
|
|
2977
|
+
|
|
2978
|
+
#### Union errors
|
|
2979
|
+
|
|
2980
|
+
Функция может бросать несколько типов ошибок:
|
|
2981
|
+
|
|
2982
|
+
```typescript
|
|
2983
|
+
function process(path: string): Response throws IOError | NetworkError {
|
|
2984
|
+
const content = readFile(path); // throws IOError
|
|
2985
|
+
return fetch(content); // throws NetworkError
|
|
2986
|
+
}
|
|
2987
|
+
```
|
|
2988
|
+
|
|
2989
|
+
Компилятор объединяет `throws`-типы автоматически при вызове функций внутри тела.
|
|
2990
|
+
|
|
2991
|
+
#### Оператор `?` — propagate
|
|
2992
|
+
|
|
2993
|
+
`expr?` — если функция вернула ошибку, немедленно вернуть её из текущей функции. Текущая функция обязана иметь совместимый `throws`:
|
|
2994
|
+
|
|
2995
|
+
```typescript
|
|
2996
|
+
function process(path: string): string throws IOError | NetworkError {
|
|
2997
|
+
const content = readFile(path)?; // propagate IOError
|
|
2998
|
+
const r = fetch(content)?; // propagate NetworkError
|
|
2999
|
+
return r.body;
|
|
3000
|
+
}
|
|
3001
|
+
```
|
|
3002
|
+
|
|
3003
|
+
Несовместимый `throws` — ошибка компилятора:
|
|
3004
|
+
|
|
3005
|
+
```typescript
|
|
3006
|
+
function main(): void {
|
|
3007
|
+
const data = readFile("x")?;
|
|
3008
|
+
// ошибка: main не объявляет throws, нельзя использовать ?
|
|
3009
|
+
}
|
|
3010
|
+
```
|
|
3011
|
+
|
|
3012
|
+
#### Оператор `!` — unwrap или panic
|
|
3013
|
+
|
|
3014
|
+
`expr!` — если функция вернула ошибку, вызвать `abort()` (runtime panic). Не требует `throws` у текущей функции:
|
|
3015
|
+
|
|
3016
|
+
```typescript
|
|
3017
|
+
function main(): void {
|
|
3018
|
+
const content = readFile("config.txt")!; // panic если ошибка
|
|
3019
|
+
console.log(content);
|
|
3020
|
+
}
|
|
3021
|
+
```
|
|
3022
|
+
|
|
3023
|
+
#### C-output
|
|
3024
|
+
|
|
3025
|
+
`throws` меняет C-сигнатуру функции: возвращаемый тип оборачивается в Result-структуру. Для `throws IOError | NetworkError`:
|
|
3026
|
+
|
|
3027
|
+
```c
|
|
3028
|
+
// Генерируется компилятором
|
|
3029
|
+
typedef enum { _ERR_IO, _ERR_NETWORK } _fetch_err_kind;
|
|
3030
|
+
|
|
3031
|
+
typedef struct {
|
|
3032
|
+
bool ok;
|
|
3033
|
+
union {
|
|
3034
|
+
Response value;
|
|
3035
|
+
struct {
|
|
3036
|
+
_fetch_err_kind _kind;
|
|
3037
|
+
union {
|
|
3038
|
+
IOError io;
|
|
3039
|
+
NetworkError net;
|
|
3040
|
+
} _err;
|
|
3041
|
+
};
|
|
3042
|
+
};
|
|
3043
|
+
} _Result_Response_IOError_NetworkError;
|
|
3044
|
+
|
|
3045
|
+
_Result_Response_IOError_NetworkError fetch(String url) { ... }
|
|
3046
|
+
```
|
|
3047
|
+
|
|
3048
|
+
`try/catch` компилируется в `if/else` по полю `ok` и `_kind`:
|
|
3049
|
+
|
|
3050
|
+
```c
|
|
3051
|
+
_Result_Response_IOError_NetworkError _r = fetch(str("https://..."));
|
|
3052
|
+
if (_r.ok) {
|
|
3053
|
+
Response r = _r.value;
|
|
3054
|
+
process(r);
|
|
3055
|
+
Response_free(r);
|
|
3056
|
+
} else if (_r._kind == _ERR_IO) {
|
|
3057
|
+
IOError e = _r._err.io;
|
|
3058
|
+
printf("IO: %s\n", e.message.data);
|
|
3059
|
+
IOError_free(e);
|
|
3060
|
+
} else if (_r._kind == _ERR_NETWORK) {
|
|
3061
|
+
NetworkError e = _r._err.net;
|
|
3062
|
+
printf("Network: %s\n", e.message.data);
|
|
3063
|
+
NetworkError_free(e);
|
|
3064
|
+
}
|
|
3065
|
+
// finally
|
|
3066
|
+
closeConnection();
|
|
3067
|
+
```
|
|
3068
|
+
|
|
3069
|
+
Оператор `?`:
|
|
3070
|
+
```c
|
|
3071
|
+
_Result_String_IOError _r = readFile(str("x"));
|
|
3072
|
+
if (!_r.ok) return (_Result_String_NetworkError){ .ok = false, ._err = ... };
|
|
3073
|
+
String content = _r.value;
|
|
3074
|
+
```
|
|
3075
|
+
|
|
3076
|
+
Оператор `!`:
|
|
3077
|
+
```c
|
|
3078
|
+
_Result_String_IOError _r = readFile(str("config.txt"));
|
|
3079
|
+
if (!_r.ok) { fprintf(stderr, "panic\n"); abort(); }
|
|
3080
|
+
String content = _r.value;
|
|
3081
|
+
```
|
|
3082
|
+
|
|
3083
|
+
#### Ownership при ошибках
|
|
3084
|
+
|
|
3085
|
+
Компилятор отслеживает все owned переменные в `try`-блоке. Если выбрасывается ошибка, все уже инициализированные owned переменные корректно освобождаются через обычный control flow — никаких специальных механизмов не нужно, так как это просто `if/else` в C:
|
|
3086
|
+
|
|
3087
|
+
```typescript
|
|
3088
|
+
function process(): void throws IOError {
|
|
3089
|
+
const a = new Foo(); // owned
|
|
3090
|
+
const b = new Bar(); // owned
|
|
3091
|
+
riskyOp()?; // если ошибка → a и b освобождаются в else-ветке
|
|
3092
|
+
use(a, b);
|
|
3093
|
+
}
|
|
3094
|
+
```
|
|
3095
|
+
|
|
3096
|
+
Генерируется:
|
|
3097
|
+
```c
|
|
3098
|
+
// try-ветка
|
|
3099
|
+
Foo* a = Foo_new();
|
|
3100
|
+
Bar* b = Bar_new();
|
|
3101
|
+
_Result_void_IOError _r = riskyOp();
|
|
3102
|
+
if (!_r.ok) {
|
|
3103
|
+
Foo_free(a); // компилятор генерирует cleanup
|
|
3104
|
+
Bar_free(b);
|
|
3105
|
+
return (_Result_void_IOError){ .ok = false, ._err = _r._err };
|
|
3106
|
+
}
|
|
3107
|
+
use(a, b);
|
|
3108
|
+
Foo_free(a);
|
|
3109
|
+
Bar_free(b);
|
|
3110
|
+
```
|
|
3111
|
+
|
|
3112
|
+
#### Ограничения
|
|
3113
|
+
|
|
3114
|
+
- `throw` запрещён в функциях без `throws` — ошибка компилятора
|
|
3115
|
+
- `?` запрещён в функции без `throws` — ошибка компилятора
|
|
3116
|
+
- Исключения нельзя бросать через C interop границы — функции, объявленные как `extern "C"`, не могут содержать `throws`
|
|
3117
|
+
- `finally` не может содержать `throw` или `return` — ошибка компилятора (неопределённое поведение)
|
|
3118
|
+
|
|
3119
|
+
### Concurrency
|
|
3120
|
+
|
|
3121
|
+
#### Уровни модели
|
|
3122
|
+
|
|
3123
|
+
TSC разделяет конкурентность на три независимых механизма:
|
|
3124
|
+
|
|
3125
|
+
| Механизм | Платформа | Уровень |
|
|
3126
|
+
|----------|-----------|---------|
|
|
3127
|
+
| `async/await` | все | стандартный |
|
|
3128
|
+
| `std/thread` | OS (desktop/server) | продвинутый |
|
|
3129
|
+
| `@interrupt` | embedded (AVR/Cortex) | системный |
|
|
3130
|
+
|
|
3131
|
+
---
|
|
3132
|
+
|
|
3133
|
+
#### 1. Async/Await — стандартный способ
|
|
3134
|
+
|
|
3135
|
+
Единственный event loop, один поток исполнения. `Shared<T>` и `Weak<T>` **не атомарны** — никаких накладных расходов. Narrowing через `if (x != null)` безопасен — между проверкой и использованием никакой другой код не выполняется.
|
|
3136
|
+
|
|
3137
|
+
```typescript
|
|
3138
|
+
async function fetchUser(id: i32): User throws NetworkError {
|
|
3139
|
+
const conn = await connect("https://api.example.com");
|
|
3140
|
+
const data = await conn.get(`/users/${id}`);
|
|
3141
|
+
return User.parse(data);
|
|
3142
|
+
}
|
|
3143
|
+
|
|
3144
|
+
async function main(): void {
|
|
3145
|
+
const user = await fetchUser(42);
|
|
3146
|
+
console.log(user.name);
|
|
3147
|
+
}
|
|
3148
|
+
```
|
|
3149
|
+
|
|
3150
|
+
На **embedded** `async fn` компилируется в state machine в C — без runtime, без heap:
|
|
3151
|
+
|
|
3152
|
+
```c
|
|
3153
|
+
// async fn → конечный автомат
|
|
3154
|
+
typedef struct { int _state; /* захваченные переменные */ } FetchUserTask;
|
|
3155
|
+
bool FetchUserTask_poll(FetchUserTask* t) { switch (t->_state) { ... } }
|
|
3156
|
+
```
|
|
3157
|
+
|
|
3158
|
+
##### Promise<T>
|
|
3159
|
+
|
|
3160
|
+
Тип возвращаемого значения `async` функции — `Promise<T>`. Обе записи эквивалентны:
|
|
3161
|
+
|
|
3162
|
+
```typescript
|
|
3163
|
+
async function fetchUser(id: i32): User { ... } // компилятор выводит Promise<User>
|
|
3164
|
+
async function fetchUser(id: i32): Promise<User> { ... } // то же самое явно
|
|
3165
|
+
```
|
|
3166
|
+
|
|
3167
|
+
Создать `Promise<T>` вручную (для оборачивания callback-based API):
|
|
3168
|
+
|
|
3169
|
+
```typescript
|
|
3170
|
+
function delay(ms: i32): Promise<void> {
|
|
3171
|
+
return new Promise((resolve, reject) => {
|
|
3172
|
+
setTimeout(() => resolve(), ms);
|
|
3173
|
+
});
|
|
3174
|
+
}
|
|
3175
|
+
|
|
3176
|
+
function readFile(path: string): Promise<string> {
|
|
3177
|
+
return new Promise((resolve, reject) => {
|
|
3178
|
+
if (!fileExists(path)) reject(new IOError("not found"));
|
|
3179
|
+
else resolve(fs.readSync(path));
|
|
3180
|
+
});
|
|
3181
|
+
}
|
|
3182
|
+
```
|
|
3183
|
+
|
|
3184
|
+
- `resolve(value)` — завершает Promise успехом, передаёт значение
|
|
3185
|
+
- `reject(error)` — завершает Promise ошибкой; тип ошибки должен совпадать с `throws`
|
|
3186
|
+
- Вызов `resolve` или `reject` после первого вызова — no-op
|
|
3187
|
+
|
|
3188
|
+
С error handling:
|
|
3189
|
+
|
|
3190
|
+
```typescript
|
|
3191
|
+
async function fetch(url: string): string throws NetworkError {
|
|
3192
|
+
return new Promise((resolve, reject) => {
|
|
3193
|
+
httpGet(url, (err, data) => {
|
|
3194
|
+
if (err) reject(new NetworkError(err));
|
|
3195
|
+
else resolve(data);
|
|
3196
|
+
});
|
|
3197
|
+
});
|
|
3198
|
+
}
|
|
3199
|
+
```
|
|
3200
|
+
|
|
3201
|
+
##### Promise.all
|
|
3202
|
+
|
|
3203
|
+
Запуск нескольких async задач параллельно:
|
|
3204
|
+
|
|
3205
|
+
```typescript
|
|
3206
|
+
const [users, posts] = await Promise.all([
|
|
3207
|
+
fetchUsers(), // Promise<User[]>
|
|
3208
|
+
fetchPosts(), // Promise<Post[]>
|
|
3209
|
+
]);
|
|
3210
|
+
|
|
3211
|
+
// с error handling — если любая задача бросает, вся группа бросает
|
|
3212
|
+
const [a, b, c] = await Promise.all([taskA(), taskB(), taskC()]);
|
|
3213
|
+
```
|
|
3214
|
+
|
|
3215
|
+
- Все задачи запускаются одновременно, ждём завершения всех
|
|
3216
|
+
- Если любая задача завершается ошибкой — `Promise.all` бросает эту ошибку, остальные отменяются
|
|
3217
|
+
- Типы элементов выводятся компилятором из переданных Promise
|
|
3218
|
+
|
|
3219
|
+
##### Правила await
|
|
3220
|
+
|
|
3221
|
+
- `await` только внутри `async` функции — иначе ошибка компилятора
|
|
3222
|
+
- `await` только на `Promise<T>` — `await` на обычном значении ошибка компилятора
|
|
3223
|
+
|
|
3224
|
+
```typescript
|
|
3225
|
+
// ✅ ok
|
|
3226
|
+
async function foo(): i32 {
|
|
3227
|
+
return await bar(); // bar(): Promise<i32>
|
|
3228
|
+
}
|
|
3229
|
+
|
|
3230
|
+
// ❌ await вне async функции
|
|
3231
|
+
function bad(): void {
|
|
3232
|
+
await foo(); // error: await outside async function
|
|
3233
|
+
}
|
|
3234
|
+
|
|
3235
|
+
// ❌ await на не-Promise
|
|
3236
|
+
async function bad2(): void {
|
|
3237
|
+
const x: i32 = 42;
|
|
3238
|
+
await x; // error: cannot await i32, expected Promise<T>
|
|
3239
|
+
}
|
|
3240
|
+
```
|
|
3241
|
+
|
|
3242
|
+
##### async main
|
|
3243
|
+
|
|
3244
|
+
Entry point может быть `async` — компилятор запускает event loop автоматически:
|
|
3245
|
+
|
|
3246
|
+
```typescript
|
|
3247
|
+
async function main(): void {
|
|
3248
|
+
const user = await fetchUser(42);
|
|
3249
|
+
console.log(user.name);
|
|
3250
|
+
}
|
|
3251
|
+
```
|
|
3252
|
+
|
|
3253
|
+
На desktop/server — стандартный event loop (libuv или аналог).
|
|
3254
|
+
На embedded — poll loop, скомпилированный в state machine без heap.
|
|
3255
|
+
|
|
3256
|
+
---
|
|
3257
|
+
|
|
3258
|
+
#### 2. Threads (std/thread) — продвинутый уровень
|
|
3259
|
+
|
|
3260
|
+
Только там где есть OS. Потоки работают как **изоляты** — без общей памяти. Связь исключительно через каналы с передачей владения.
|
|
3261
|
+
|
|
3262
|
+
```typescript
|
|
3263
|
+
import { Thread, channel } from "std/thread";
|
|
3264
|
+
|
|
3265
|
+
async function main(): void {
|
|
3266
|
+
const [tx, rx] = channel<i32[]>();
|
|
3267
|
+
|
|
3268
|
+
const t = Thread.spawn(() => {
|
|
3269
|
+
// тяжёлые вычисления в отдельном потоке
|
|
3270
|
+
const result = heavyComputation();
|
|
3271
|
+
tx.send(result); // move — передача владения через канал
|
|
3272
|
+
});
|
|
3273
|
+
|
|
3274
|
+
const result = await rx.recv(); // ждём результат из async-кода
|
|
3275
|
+
t.join();
|
|
3276
|
+
console.log(result);
|
|
3277
|
+
}
|
|
3278
|
+
```
|
|
3279
|
+
|
|
3280
|
+
**Правила передачи в Thread.spawn:**
|
|
3281
|
+
|
|
3282
|
+
| Тип | Разрешено | Поведение |
|
|
3283
|
+
|-----|-----------|-----------|
|
|
3284
|
+
| Owned `T` | ✅ | неявный move |
|
|
3285
|
+
| Примитив | ✅ | copy |
|
|
3286
|
+
| `Ref<T>` / `Mut<T>` | ❌ | ошибка компилятора |
|
|
3287
|
+
| `Shared<T>` / `Weak<T>` | ❌ | ошибка компилятора |
|
|
3288
|
+
| `Readonly<T>` | ➡️ | v2 (не реализовано) |
|
|
3289
|
+
|
|
3290
|
+
**Global State в контексте потоков:**
|
|
3291
|
+
|
|
3292
|
+
```typescript
|
|
3293
|
+
const CONFIG = { maxRetries: 3 }; // const — ok, читать из потоков можно
|
|
3294
|
+
let counter = 0; // ошибка компилятора если Thread.spawn захватывает
|
|
3295
|
+
const ac = new Atomic<i32>(0); // Atomic<T> — ok из потоков
|
|
3296
|
+
|
|
3297
|
+
class Server {
|
|
3298
|
+
static count: i32 = 0; // mutable static — ошибка при захвате в Thread.spawn
|
|
3299
|
+
static readonly MAX: i32 = 100; // const static — ok
|
|
3300
|
+
}
|
|
3301
|
+
```
|
|
3302
|
+
|
|
3303
|
+
Компилятор проверяет захваченные переменные **на границе `Thread.spawn`**:
|
|
3304
|
+
- Мутабельный `let` или глобаль → ошибка компилятора
|
|
3305
|
+
- `Shared<T>` или `Weak<T>` → ошибка компилятора
|
|
3306
|
+
- Owned `T` → неявный move
|
|
3307
|
+
- Примитив → copy
|
|
3308
|
+
|
|
3309
|
+
---
|
|
3310
|
+
|
|
3311
|
+
#### 3. @interrupt — только Embedded
|
|
3312
|
+
|
|
3313
|
+
ISR — аппаратное прерывание. Не поток, не closure. Никакого захвата контекста, только работа с `@volatile` регистрами и `Atomic<T>`:
|
|
3314
|
+
|
|
3315
|
+
```typescript
|
|
3316
|
+
@interrupt("TIMER0_OVF")
|
|
3317
|
+
function onTimer(): void {
|
|
3318
|
+
PORTB ^= 0x01; // toggle LED — только hardware registers
|
|
3319
|
+
}
|
|
3320
|
+
|
|
3321
|
+
@interrupt("USART_RX")
|
|
3322
|
+
function onReceive(): void {
|
|
3323
|
+
const byte = UDR0; // volatile register read
|
|
3324
|
+
rxBuffer.push_atomic(byte); // Atomic<T> — ok
|
|
3325
|
+
}
|
|
3326
|
+
```
|
|
3327
|
+
|
|
3328
|
+
- Доступ к обычным переменным программы — ошибка компилятора
|
|
3329
|
+
- Доступ к `Shared<T>`, `Ref<T>`, owned объектам — ошибка компилятора
|
|
3330
|
+
- Доступ к `@volatile` и `Atomic<T>` — разрешён
|
|
3331
|
+
|
|
3332
|
+
`std/thread` на embedded targets — ошибка компилятора (нет OS).
|
|
3333
|
+
|
|
3334
|
+
---
|
|
3335
|
+
|
|
3336
|
+
#### Итоговая картина
|
|
3337
|
+
|
|
3338
|
+
```
|
|
3339
|
+
┌─────────────────────────────────────────────────────┐
|
|
3340
|
+
│ TSC Concurrency Model │
|
|
3341
|
+
│ │
|
|
3342
|
+
│ async/await ──── event loop ──── все платформы │
|
|
3343
|
+
│ │ │
|
|
3344
|
+
│ └── Shared<T>/Weak<T> не атомарны │
|
|
3345
|
+
│ └── Weak narrowing безопасен │
|
|
3346
|
+
│ │
|
|
3347
|
+
│ std/thread ───── isolates ────── OS only │
|
|
3348
|
+
│ │ │
|
|
3349
|
+
│ └── только owned/primitive через каналы │
|
|
3350
|
+
│ └── компилятор проверяет на Thread.spawn │
|
|
3351
|
+
│ │
|
|
3352
|
+
│ @interrupt ────── ISR ─────────── embedded only │
|
|
3353
|
+
│ │ │
|
|
3354
|
+
│ └── только @volatile + Atomic<T> │
|
|
3355
|
+
│ └── нет захвата контекста │
|
|
3356
|
+
└─────────────────────────────────────────────────────┘
|
|
3357
|
+
```
|
|
3358
|
+
|
|
3359
|
+
### Standard Library
|
|
3360
|
+
|
|
3361
|
+
> TBD
|
|
3362
|
+
|
|
3363
|
+
---
|
|
3364
|
+
|
|
3365
|
+
## Open Questions
|
|
3366
|
+
|
|
3367
|
+
- **Стандартная библиотека — что входит:**
|
|
3368
|
+
- `std/io` — консоль, файлы
|
|
3369
|
+
- `std/fs` — файловая система
|
|
3370
|
+
- `std/net` — сеть, HTTP
|
|
3371
|
+
- `std/math` — математика
|
|
3372
|
+
- `std/string` — утилиты строк
|
|
3373
|
+
- `std/collections` — дополнительные коллекции
|
|
3374
|
+
- `std/thread` — потоки (уже есть)
|
|
3375
|
+
- `std/env` — переменные окружения, аргументы
|
|
3376
|
+
- `std/time` — работа со временем
|
|
3377
|
+
- **Concurrency (детальное обсуждение):**
|
|
3378
|
+
- Async/await runtime — libuv или своя event loop реализация?
|
|
3379
|
+
- `Atomic<T>` — какие операции? (`load`, `store`, `fetch_add`, CAS?)
|
|
3380
|
+
- `channel<T>` — bounded vs unbounded? backpressure?
|
|
3381
|
+
- `@interrupt` — как именно объявляются `@volatile` регистры?
|
|
3382
|
+
- `Readonly<T>` (v2) — глубокая иммутабельность для передачи между потоками
|
|
3383
|
+
- Structured concurrency — нужны ли `TaskGroup` / `withTaskGroup`?
|
|
3384
|
+
- Отмена задач — `AbortSignal` или другой механизм?
|
|
3385
|
+
|
|
3386
|
+
---
|
|
3387
|
+
|
|
3388
|
+
## Roadmap
|
|
3389
|
+
|
|
3390
|
+
### Этап 0: Подготовка
|
|
3391
|
+
|
|
3392
|
+
Дальше используется термин `Кодовая база` - это файл, в котором описывается:
|
|
3393
|
+
- `реализация` на JS, включая методы
|
|
3394
|
+
- `реализация` на C, вплоть до каждого метода
|
|
3395
|
+
- `реализация` на TSC, вплоть до каждого метода
|
|
3396
|
+
|
|
3397
|
+
Для каждой `реализации`:
|
|
3398
|
+
- пример кода
|
|
3399
|
+
- результат, который код должен выдавать
|
|
3400
|
+
- пример кода с ошибкой
|
|
3401
|
+
- какую ошибку должен выдавать
|
|
3402
|
+
- пример исправления ошибки
|
|
3403
|
+
- результат, который должен быть после исправления
|
|
3404
|
+
|
|
3405
|
+
Примерная структура документации к проекту:
|
|
3406
|
+
|
|
3407
|
+
```
|
|
3408
|
+
doc/
|
|
3409
|
+
index.md
|
|
3410
|
+
1_перегрузка_функций/
|
|
3411
|
+
1_1_перегрузка_по_типам.md
|
|
3412
|
+
1_2_перегрузка_по_количеству.md
|
|
3413
|
+
```
|
|
3414
|
+
|
|
3415
|
+
Примерная структура файла `index.md`:
|
|
3416
|
+
|
|
3417
|
+
```
|
|
3418
|
+
# Раздел 1. Перегрузка функций
|
|
3419
|
+
|
|
3420
|
+
## Правило 1. Перегрузка по типам.
|
|
3421
|
+
## Правило 2. Перегрузка по количеству.
|
|
3422
|
+
|
|
3423
|
+
...
|
|
3424
|
+
```
|
|
3425
|
+
|
|
3426
|
+
Примерная структура файла `1_1_перегрузка_по_типам.md`:
|
|
3427
|
+
|
|
3428
|
+
```
|
|
3429
|
+
# Раздел 1. Перегрузка функций
|
|
3430
|
+
|
|
3431
|
+
## Правило 1
|
|
3432
|
+
|
|
3433
|
+
Перегрузка по типам.
|
|
3434
|
+
|
|
3435
|
+
Код на typescript
|
|
3436
|
+
|
|
3437
|
+
```typescript
|
|
3438
|
+
...
|
|
3439
|
+
```
|
|
3440
|
+
|
|
3441
|
+
Код на c
|
|
3442
|
+
|
|
3443
|
+
```c
|
|
3444
|
+
...
|
|
3445
|
+
```
|
|
3446
|
+
|
|
3447
|
+
Код на tsc
|
|
3448
|
+
|
|
3449
|
+
```
|
|
3450
|
+
...
|
|
3451
|
+
```
|
|
3452
|
+
|
|
3453
|
+
Результат выполнения
|
|
3454
|
+
|
|
3455
|
+
```
|
|
3456
|
+
...
|
|
3457
|
+
```
|
|
3458
|
+
|
|
3459
|
+
Ошибка, если перегрузка будет содержать необязательный параметр
|
|
3460
|
+
|
|
3461
|
+
Код на tsc
|
|
3462
|
+
|
|
3463
|
+
```
|
|
3464
|
+
...
|
|
3465
|
+
```
|
|
3466
|
+
|
|
3467
|
+
Вывод с подсказкой
|
|
3468
|
+
|
|
3469
|
+
```
|
|
3470
|
+
...
|
|
3471
|
+
```
|
|
3472
|
+
|
|
3473
|
+
Чтобы исправить, нужно сделать ...
|
|
3474
|
+
|
|
3475
|
+
Код на tsc
|
|
3476
|
+
|
|
3477
|
+
```
|
|
3478
|
+
...
|
|
3479
|
+
```
|
|
3480
|
+
|
|
3481
|
+
Результат выполнения
|
|
3482
|
+
|
|
3483
|
+
```
|
|
3484
|
+
...
|
|
3485
|
+
```
|
|
3486
|
+
|
|
3487
|
+
- [ ] Создать каталог `doc` в котором будет хранится документация по проекту
|
|
3488
|
+
- [ ] Создать внутри файл `index.md`
|
|
3489
|
+
- [ ] Разбить весь концепт на ключевые разделы
|
|
3490
|
+
- [ ] Для каждого раздела создать свой каталог в `doc`
|
|
3491
|
+
- [ ] Записать раздел в файл `index.md`
|
|
3492
|
+
- [ ] Разделы разбить на правила и операторы
|
|
3493
|
+
- [ ] Каждое правило и оператор записать в каталог раздела, в файл вида `[номер_раздела]_[название_раздела].md`
|
|
3494
|
+
- [ ] Собрать под каждое правило и оператор кодовую базу и записать в тот же файл
|
|
3495
|
+
|
|
3496
|
+
### Этап 1: Инфраструктура
|
|
3497
|
+
- [ ] Парсер (свой или swc)
|
|
3498
|
+
- [ ] AST → IR lowering
|
|
3499
|
+
- [ ] ScopeManager: ALIVE, MOVED, DROPPED
|
|
3500
|
+
|
|
3501
|
+
### Этап 2: Кодогенерация структур
|
|
3502
|
+
- [ ] class/interface → struct в C
|
|
3503
|
+
- [ ] `_free` деструкторы
|
|
3504
|
+
|
|
3505
|
+
### Этап 3: Ownership анализ
|
|
3506
|
+
- [ ] Move при присваивании
|
|
3507
|
+
- [ ] Вставка `free()` в конце блоков
|
|
3508
|
+
- [ ] Эпилог cleanup
|
|
3509
|
+
|
|
3510
|
+
### Этап 4: Borrow Checker
|
|
3511
|
+
- [ ] `Ref<T>` / `Mut<T>` проверки
|
|
3512
|
+
- [ ] Scope Constraint
|
|
3513
|
+
|
|
3514
|
+
### Этап 5: ARC
|
|
3515
|
+
- [ ] Runtime: `RC_retain` / `RC_release`
|
|
3516
|
+
- [ ] `Shared<T>` тип
|
|
3517
|
+
- [ ] `Weak<T>` тип — weak ref, optional chaining при доступе
|