Generic Containers in C: Safe Division Using Maybe
Показываю, как в C сделать обобщённый контейнер maybe
, который безопасно возвращает результат, если он есть, и сообщает об ошибке, если её нет.
#define maybe(T) struct { bool ok; T value; }
#define maybe_just(T,x) { .value = (x), .ok = true }
#define maybe_nothing(T) { .value = (T){}, .ok = false }
static maybe(int) divide(int a, int b) {
return (b != 0) ? maybe_just(int, a / b) : maybe_nothing(int);
}
Вызов:
maybe(int) p = divide(6, d);
if (p.ok) printf("%d\n", p.value);
else puts("division by zero");
Чтобы не забыть проверку, добавляем макрос maybe_value
, который при ошибке возвращает нулевой указатель, ловим его санитайзером:
#define maybe_value(x) \
(*({ auto _p = &(x); _p->ok ? &_p->value : (void*)0; }))
Но деление на ноль — не единственная проблема. При делении INT_MIN / -1
возникает переполнение. Исправляем:
maybe(int) safe_divide(int a, int b) {
if (b == 0 || (b == -1 && a == INT_MIN))
return maybe_nothing(int);
return maybe_just(int, a / b);
}
Компилируем с -fsanitize=signed-integer-overflow,integer-divide-by-zero -fsanitize-trap=undefined -O2
. В ассемблере не остаётся пути к ud2
, то есть оптимизатор доказал: переполнения и деления на ноль нет.
Это не делает весь C «безопасным» (жизненный цикл указателей и арифметика не покрыты), но для ограниченных задач подход работает.
Комментарии (46)
- Критика: реализация не заставляет проверять результат, теряя главное преимущество Maybe.
- Ассемблер: GCC выдаёт почти тот же код, что и для std::optional, но не возвращает результат в регистре.
- UB: «пустой» lvalue в случае ошибки вызывает неопределённое поведение; автор полагается на null-sanitizer.
- Эргономика: предлагают добавить and_then/or_else и сделать тип непрозрачным через макросы.
- Почему не другой язык: встраиваемые/фирменные проекты часто ограничены только C или ASM.
Comptime.ts: compile-time expressions for TypeScript
Простой компилятор TypeScript для вычисления выражений с пометкой comptime на этапе сборки. Полезно для переноса вычислений из рантайма в компиляцию. Вдохновлено Bun macros и Zig comptime.
Внимание: вы сами отвечаете за безопасность выражений, вычисляемых на этапе компиляции. Изоляции нет. Импорты comptime допускаются только в файлах проекта (не в node_modules), но можно импортировать из node_modules как comptime.
Содержание
- Что такое comptime.ts?
- Примеры: 1) простая сумма; 2) CSS без рантайма; 3) константы во время сборки
- Установка
- Использование: Vite, Bun, CLI, API
- Принудительная оценка и промисы, отказ от «вирусности»
- Запуск кода после comptime, как работает, ограничения, практики, отладка, поддержка, лицензия
Что это comptime.ts вычисляет выражения при компиляции, сокращая работу в рантайме.
Примеры
-
Простая сумма import { sum } from "./sum.ts" with { type: "comptime" }; console.log(sum(1, 2)); // => console.log(3);
-
Emotion CSS без рантайма import { css } from "@emotion/css" with { type: "comptime" }; const style = css
color: red; font-size: 16px;
; div({ class: style }); // => const style = "css-x2wxma"; div({ class: style });
Примечание: импорт @emotion/css удаляется. Стили нужно вывести отдельно (после comptime или плагином бандлера).
- Константы на этапе сборки import { ms } from "ms" with { type: "comptime" }; const HOUR = ms("1 hour"); // => const HOUR = 3600000;
Поддерживаются многие выражения (включая индексацию и импортированные константы), результат должен быть сериализуем в JSON. Импорты с type: "comptime" удаляются; лишнее убирает ваш бандлер.
Установка bun add comptime.ts pnpm add comptime.ts npm install comptime.ts
Использование
- Vite: import { comptime } from "comptime.ts/vite"; export default defineConfig({ plugins: [comptime()] });
Только в прод-сборке, если поведение совпадает с рантаймом: export default defineConfig({ build: { rollupOptions: { plugins: [comptime()] } } });
- Bun: import { comptime } from "comptime.ts/bun"; await Bun.build({ entrypoints: ["./index.ts"], ou ... })
Комментарии (30)
- Обсуждение крутится вокруг идеи “comptime”/макросов в JS: часть хочет Rust‑подобные макросы и proc‑макросы (вплоть до JSX как jsx! или вообще писать фронт на Rust/wasm), другая сторона категорически против макросов в TS/JS.
- Есть путаница в терминах: “макросы” vs “comptime”; участники критикуют переиспользование терминов и вспоминают неудачный опыт sweet.js.
- Практические вопросы: можно ли делать агрессивный dead‑code elimination через if (comptime …) как в C препроцессоре? Ответ: само comptime подставит true/false, но для выкидывания веток нужен отдельный шаг минификатора/бандлера (Vite/Bun поддержат).
- Дискуссия об импорте with { type: 'comptime' }: одни считают это неправильным использованием атрибута type (ожидается соответствие MIME), другие указывают, что спецификация оставляет семантику type открытой.
- Обсуждают границы возможности: поддержка типов/генериков на уровне comptime (как в Zig) пока ограничена; возврат именованных функций и сложные случаи с замыканиями не поддерживаются из‑за требований к гарантиям и сохранению функций между процессами.
- Альтернативы: настроить сборку для JSX без макросов; использовать библиотеки вроде lite-jsx; для Rust‑фронта рекомендуют Dioxus/Leptos; спорят о реальной применимости wasm и памяти/управления ей в вебе.
- Применимость: идея удобна для предсборки (например, markdown) и константной подстановки, но не заменяет полноценных препроцессоров/макросистем уровня Rust/Zig.