LINUX.ORG.RU

Zig 0.16 - async здорового человека

 ,


1

7

Zig 0.16 закрыл проблему раскраски функций: sync и async теперь имеют одинаковую сигнатуру, отличается только переданный I/O-интерфейс. Если вы писали асинхронный Rust и упирались в async fn-заражение - посмотрите, как это решено здесь.
async без раскраски функций - это то, на чём подрывались Rust, JavaScript и C#. Асинхронная функция ничем не отличается по сигнатуре от синхронной - меняется только переданный Io.

Ну и всякого по мелочам:

  1. «Juicy Main» это внутреннее название proposal Эндрю Келли - идея в том, что точка входа должна получать «сочный» набор готовых зависимостей, а не создавать их внутри. DI здесь значит что аллокатор, I/O, env и preopens передаются в main снаружи, а не конструируются каждым приложением заново. Раньше каждая программа на Zig начиналась с одного и того же бойлерплейта: создать GeneralPurposeAllocator, получить arg-итератор через std.process.argsAlloc, отдельно дёрнуть std.process.getEnvMap. В 0.16 всё это подаётся параметром в main
  2. В C setenv в многопоточной программе это UB: глобальная environ читается без блокировок. Zig до 0.16 наследовал эту проблему через std.os.environ, который ещё и нельзя было заполнить без линковки libc.
    Теперь окружение доступно только из main через init.environ_map. Если библиотеке нужен env - она принимает его параметром, как аллокатор. Это ломает код, дёргавший std.process.getEnvVarOwned из произвольного места, но убирает целый класс thread-safety-багов.
    Параллельно переименованы функции в std.mem: indexOf → find, добавлены cut / cutScalar для разбиения слайсов по первому/последнему вхождению.
  3. В 0.16 появился собственный ELF-линкер, он включается флагом -fnew-linker, а при -fincremental на self-hosted ELF-сборке используется автоматически. Он пока не feature-complete (например, не пишет DWARF), поэтому по умолчанию release-сборки идут через LLVM + LLD. Выгода - инкрементальная линковка (194мс → 65мс на тестовом проекте) и меньше зависимостей для debug-сборок.
  4. доработки под Windows: сетевой стек теперь работает без ws2_32.dll (напрямую через NtDll), завершена миграция с Win32 API на NtDll для остальных системных вызовов, появился inter-process progress reporting для параллельных сборок.
  5. x86-бэкенд компилятора стал самодостаточным - дебажные сборки на x86_64 собираются без LLVM и идут значительно быстрее. aarch64-бэкенд ещё work-in-progress: в 0.16 он падает на behavior-тестах.
  6. Инкрементальная компиляция переработана, меньше false-rebuilds, стабильнее на больших проектах.
  7. Fuzzer (zig test –fuzz) получил multi-process режим, infinite mode и crash dumps с AST-дампом.
  8. Build-система: локальный override пакетов, –error-style и –multiline-errors, таймауты юнит-тестов, temporary files API.
  9. Крипто: добавлены AES-SIV, AES-GCM-SIV, Ascon-AEAD, Ascon-Hash, Ascon-CHash.
  10. Heap: ArenaAllocator стал thread-safe и lock-free, обёртка ThreadSafeAllocator удалена.
  11. Тулчейн: LLVM 21 (с отключённой loop vectorization из-за регрессии), musl 1.2.5, glibc 2.43, Linux 6.19 headers, macOS 26.4 headers, MinGW-w64, FreeBSD 15.0 libc

Поздравляю разработчиков, пользователей, интересующихся и вообще - всех причастных.
Ура!

★★★★

Последнее исправление: dataman (всего исправлений: 1)
Ответ на: комментарий от ya-betmen

новомодном dart

Поржал

ну а чо, я чувак старорежимный, я еще помню как до 17-то в пещерах охоту на оленей рисовали и мамонтовое мясо по пещерам распределяли… кхе-кхе… нд-а-а-а…. так о чем я? златая цепь на дубе том…. а, про дарт!
ты прикинь какой для меня это свежак, буквально пару секунд как запилили! свежайшая новость тысячелетия! а ты говоришь…

olelookoe ★★★★
() автор топика
Ответ на: комментарий от ya-betmen

Я лет 10 назад к этому дарту патчил заброшенный уже к тому времени модуль для эндженикса, чтобы сайтец на нем напилить.

Фигасе.
А я только-только, буквально вот-вот, успел флаттером восторгнуться.
Прикольную штуку они организовали, хоть и поперек всей индустрии. Но прикольно.

olelookoe ★★★★
() автор топика
Ответ на: комментарий от olelookoe

Ну в тот момент он выглядел как адекватная замена жсу, с возжностью прозрачной трансляции в жс клиентской части.

ya-betmen ★★★★★
()
Последнее исправление: ya-betmen (всего исправлений: 1)
Ответ на: комментарий от olelookoe

что такое «async заражение» - в курсе, нет?

Писал много кода на JS, никаких проблем с async не испытывал. Нет никакого async заражения. По-мне async в JS реализован просто идеально и ничего лучше уже не придумать. Всем надо просто копировать это как есть.

vbr ★★★★★
()
Последнее исправление: vbr (всего исправлений: 1)
Ответ на: комментарий от vbr

Писал много кода на JS, никаких проблем с async не испытывал.

По-мне async в JS реализован просто идеально и ничего лучше уже не придумать.

Значит мало писал.

В JS промисах есть противная асимметрия – когда запускается промис, он выполняет какую-то работу и результат этой работы хранится внутри до тех пор, пока его потребуешь при помощи await.

Но если внутри промиса происходит ошибка, то она должна быть обработана сразу. Если нет на промисе обработчика то возникает Unhandled Promise Rejection.

Вот пример простого типично кода: загружаем откуда-то файлы параллельно запуская их обработку и дожидаемся результатов обработки. Все в try-catch, ни к чему не подкопаться. Но, зараза, примерно каждый 5й раз все наворачивается с эксепшеном без какого-либо стектрейса. Браузеры такое проглатывают, но современные NodeJS останавливают весь процесс и надпись «all files processed» не появляется.

function randWait(sec: number): Promise<void> {
    return new Promise((resolve, reject) => {
        setTimeout(resolve, sec * 1000 * Math.random())
    })
}

async function loadFile(): Promise<{}> {
    await randWait(0.5)

    console.log('file loaded')

    return {}
}

async function processFile(file: {}): Promise<void> {
    await randWait(2)

    if (Math.random() < 0.3) {
        throw new Error('failed to process file')
    }

    console.log('file processed')
}

async function main(): Promise<void> {
    const promises: Promise<void>[] = []

    try {
        for (let i = 0; i < 5; ++i) {
            const file = await loadFile()

            // накапливаем промисы в массиве
            promises.push(processFile(file))
        }

        // параллельно дожидаемся результатов обработки
        const values = await Promise.allSettled(promises)
        console.dir(values)
    } catch(err) {
        console.error(`catch error ${err}`)
    }

    console.log('all files processed')
}

main()

Попробуй отследи такие вещи в больших проектах. allSettled навешивает свой обработчик ошибок на каждый промис, который он обрабатывает, но до этого нужно еще дожить.

В тех же плюсах, например, если внутри Future выскакивает неперехваченное исключение, оно сохраняется внутри и при вызове метода get() воспроизводится еще раз для того, чтобы можно было его нормально обработать. Что курили разработчики JS во время разработки их async/await не понятно.

Darfin
()
Ответ на: комментарий от Darfin

Значит мало писал.

Достаточно.

В JS промисах есть противная асимметрия – когда запускается промис, он выполняет какую-то работу и результат этой работы хранится внутри до тех пор, пока его потребуешь при помощи await.

Но если внутри промиса происходит ошибка, то она должна быть обработана сразу.

Глупости. Нет никакой асимметрии и ошибку можно обработать когда угодно и сколько угодно раз.

Если нет на промисе обработчика то возникает Unhandled Promise Rejection.

Ну возникнет и возникнет, в чём проблема-то? Это просто упрощение отладки и ни на что не влияет кроме появления строчки в логах.

Если тебя это напрягает - добавь пустой обработчик сразу, это уберёт unhandled promise rejection, но при этом ты всё так же сможешь обработать ошибку поздней.

vbr ★★★★★
()
Ответ на: комментарий от vbr

Ну возникнет и возникнет, в чём проблема-то? Это просто упрощение отладки и ни на что не влияет кроме появления строчки в логах.

Почему же тогда у меня на этом скрипте современная nodejs падает?

Darfin
()
Ответ на: комментарий от Darfin

Касательно твоего кода - он кривой, поэтому и работает кое-как. Или ставь await processFile или делай нормальную параллельность. Сейчас ты файлы грузишь последовательно, но при этом зачем-то пытаешься запускать processFile параллельно с загрузкой следующего файла. Твой try-catch не имеет смысла, т.к. Promise.allSettled не падает, а ты, судя по всему, рассчитывал на это.

vbr ★★★★★
()
Ответ на: комментарий от Darfin

Почему же тогда у меня на этом скрипте современная nodejs падает?

Потому, что nodejs проектировали идиоты. Но ты можешь запускать свой скрипт через node --unhandled-rejections=none

vbr ★★★★★
()
Ответ на: комментарий от vbr

Или ставь await processFile или делай нормальную параллельность.

Так такая задача. Файлы грузятся последовательно, но начинают обрабатываться как можно раньше, именно в этом и есть задача асинхронности. В конце делается ожидание завершения всей работы. Что может быть проще. allSettled возвращает массив с результатами или ошибками.

Как исправить этот код, чтобы не засиралась браузерная консоль и не падала нода без костылей, вроде флагов и глобальных обработчиков unhandled rejections?

Darfin
()
Ответ на: комментарий от Darfin

Не проверял, но примерно идея, думаю, понятна, как-то так можешь свой код переписать:

async function loadAndProcess() {
  async file = await loadFile();
  async result = await processFile(file);
  return result;
}

async function main(): Promise<void> {
  const promises: Promise<void>[] = []
  for (let i = 0; i < 5; ++i) {
    const promise = loadAndProcess();
    promises.push(processPromise)
  }
  const values = await Promise.allSettled(promises)
  console.dir(values)
  console.log('all files processed')
}

Ну или просто const promise = loadFile().then(processFile)

Тут вся фишка в том, чтобы вызвать Promise.allSettled моментально, в том же тике, что и создание Promise-ов. Он уже сам проставит им reject обрабтичики и всё везде будет работать нормально.

То, что ты написал изначально - справедливо, но это просто идиотизм конкретной реализации в node.js, а не проблема языка. В самом языке никакой необходимости немедленно добавлять обработчик к promise не имеется. Я немного погуглил и, собственно, node.js это относительно недавно поменяли, изначально такого поведения не было. Мотивировали заботой об новичках, которые забывают проверять ошибки.

vbr ★★★★★
()
Ответ на: комментарий от Darfin

Как исправить этот код

Если настаиваешь именно на таком поведении, то просто присобачь пустой обработчик:

            const file = await loadFile()
            const processPromise = processFile(file);
            processPromise.catch(()=>{});
            promises.push(processPromise)

Может как-то поэлегантней можно решить, но мне сходу в голову ничего не приходит.

vbr ★★★★★
()
Последнее исправление: vbr (всего исправлений: 1)
Ответ на: комментарий от vbr

но в случае

let p = new Promise((_, rej) => rej("e").catch(() => {}));
let ps = await Promise.allSettled([p]);
console.log(ps);

Ошибки начинают подавляться.

Т.е. ты навешиваешь catch на промис, этот catch возвращает тебе новый промис, но ты его игнорируешь и продолжаешь использовать старый промис без обработчика. Но этот старый промис уже помечен, что на нем висит обработчик, хотя и в реальности не висит, поэтому нет unhandled rejection, но при этом сами ошибки продолжают лететь.

Где-нибудь этому внятное описание сеть?

Darfin
()
Ответ на: комментарий от Darfin

В MDN есть отличное описание всего API, связанного с веб-технологиями.

https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Promise

В частности описание метода catch:

https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Promise/catch

Результат catch это Promise, который либо fulfilled с возвращенным значением из функции, переданной в catch, либо rejected, если переданная функция выбросила исключение. Ну т.е. в случае пустой функции это будет fulfilled promise с undefined значением.

Вроде всё чётко и понятно.

Если вдруг не понимаешь этого момента - на один промис можно навешивать сколько угодно catch-ей и тд. И все они будут вызваны. Т.е. в данном случае мы навешиваем пустой catch только чтобы эту диагностику подавить, а в дальнейшем allSettled повесит свой обработчик рядом с нашим пустым, и это всё сработает как ожидается.

Бездумно чейнить вызовы не стоит, это да. Надо понимать, что где возвращается. Но блин, там API буквально из трёх методов состоит. И это можно сказать базовое API для современного JS. Можно один раз и прочитать.

vbr ★★★★★
()
Последнее исправление: vbr (всего исправлений: 1)