Lekcje z przeniesienia projektu Bun na język Rust za pomocą sztucznej inteligencji: weryfikacja to prawdziwa praca
To, czego uczy nas ruch Bun’a przy pomocy agenta z Zig do Rust – dotyczący zestawów testowych jako umów, przewodników portowania, niebezpiecznego kodu oraz tego, dlaczego weryfikacja obecnie ogranicza programowanie przy użyciu AI.
Źródła: Bun Rewrote Its Runtime in Rust With AI. The Real Story Is Bigger Than the Code
Większość zespołów inżynieryjnych traktuje przepisanie języka w dużym systemie produkcyjnym jako coś, czego należy unikać tak długo, jak to możliwe. Bun, środowisko wykonawcze i zestaw narzędzi dla JavaScript i TypeScript, jak donoszą źródła, przeniósł swoją implementację z Zig na Rust, przy czym większość pracy nad tłumaczeniem wykonały agenty kodujące oparte na sztucznej inteligencji, a uzyskane wyniki są imponujące. Bardziej interesująca jest jednak nie ilość kodu stworzonego przez te agenty, lecz sposób, w jaki cały proces decydował o tym, który z tego kodu można jest zaufać. Ten tekst opisuje ten proces i przekształca go w praktyki, które możesz zastosować we własnych operacjach refaktoryzacji i migracji wspomaganych przez sztuczną inteligencję.
Dlaczego zwykle unika się przepisywań
Zmiana języka programowania wiąże się z typowym zestawem ryzyk. Może ona spowodować subtelne regresje, których nikt nie zauważy przez miesiące, zmienić wydajność w nieoczekiwanych kierunkach, uszkodzić platformę używaną jedynie przez nielicznych użytkowników oraz pochłonąć znaczną część zasobów zespołu. W najgorszym przypadku zespół musi utrzymywać zarówno stary, jak i nowy wariant implementacji równocześnie, płacąc dwa razy za każdą poprawkę błędu.
Dlatego migracja na Bun przyciąga uwagę. W chwili pisania tego tekstu repozytorium projektu opisuje Bun jako środowisko wykonawcze JavaScript oparte na Rust, zbudowane na bazie JavaScriptCore, a Anthropic poinformował o przejęciu Bun w grudniu 2025 roku. Oficjalne raporty podają, że przeniesienie kodu obejmowało około 535 000 linii języka Zig, przetłumaczonych w ciągu około 11 dni, przy czym agenty zajmowały się większością prac implementacyjnych w ramach ściśle określonego procesu.
Jest kuszące potraktowanie tego jako kolejnego tytułu w stylu „Sztuczna inteligencja napisała ogromną ilość kodu”. Lepiej postawić zupełnie inne pytanie: czy system sztucznej inteligencji może wprowadzić bardzo duże zmiany, gdy ludzie stworzą skuteczny mechanizm weryfikacji poprawności wyniku? To pytanie ma o wiele większe znaczenie dla sposobu tworzenia oprogramowania w najbliższych latach.
Port, a nie przebudowa
Bun został początkowo napisany głównie w języku Zig, języku systemowym cenionym za kontrolę na niskim poziomie i przewidywalną wydajność. Migracja nie była traktowana jako nowy początek. Jej cel był celowo wąski: zachować istniejącą architekturę i widoczne zachowanie, zmieniając jedynie język, w którym napisana jest implementacja.
To podejście do projektowania jest kluczową decyzją. Konwencjonalna przeróbka zachęca inżynierów do ciągłego przeprojektowywania – wymieniają one struktury danych, przestawiają moduły, dodają abstrakcje i dostosowują interfejsy API. Każda z tych decyzji stanowi nowe źródło ryzyka oraz coś, co trzeba przeanalizować. Wykluczając większość z nich, zespół Bun przekształcił otwartą, kreatywną zadanie w zadanie mechaniczne z jednoznacznym punktem odniesienia: obecną implementacją oraz testami, które ją opisują.
Liczba wierszy wydaje się trudnym elementem, ale tak nie jest. Tworzenie składniowo poprawnego kodu w języku Rust jest stosunkowo łatwe. Prawdziwy problem polega na zapewnieniu, że Rust będzie zachowywał się dokładnie tak samo jak Zig, który zastępuje.
Zestaw testów jako umowa behawioralna
Załóżmy, że agent otrzymuje jedynie polecenie „przepisz tę bazę kodu na Rust”. To polecenie pozostawia otwarte wszystkie ważne decyzje. Które części mogą ulec zmianie, a które muszą pozostać identyczne? Jakie skróty są do przyjęcia? Które zachowania pamięci mają kluczowe znaczenie? A przede wszystkim, skąd ktoś ma wiedzieć, że wynik jest poprawny?
Bun już miał odpowiedź na ostatnie pytanie: dużą, dojrzałą bazę testów. Gdy testy stają się umową, agent nie musi już wymyślać implementacji – on jedynie odtwarza zachowanie, które zostało już zapisane w formie wykonywalnej, a każdy plik, który tworzy, albo spełnia tę umowę, albo nie.
Czego testy mogą, a czego nie mogą obiecywać
Zestaw testów nie jest dowodem poprawności. Sprawdza jedynie zachowania, które ktoś chciał zaimplementować, a plik przetłumaczony może przejść wszystkie testy, mimo że różni się pod względem ścieżki, której nikt nie wykorzystał. Testy zapewniają natomiast pętlę zwrotną znacznie silniejszą niż prośba o ocenę własnego wyniku przez model. Bez nich wygenerowana wersja może wyglądać całkowicie wiarygodnie, jednocześnie po cichu zmieniając semantykę. Dzięki nim każda wygenerowana zmiana musi uzasadnić swoje istnienie.
Raportowany proces Bun łączył ze sobą kilka etapów: kompilację, zestaw testów, weryfikację na obsługiwanych platformach, przegląd kodu oraz dalszą walidację. Model generował kandydatów; proces inżynieryjny decydował, które z nich przetrwają. To rozdzielenie zadań stanowi kluczową ideę do zapamiętania.
Dlaczego przewodnik portowania był równie ważny jak model
Zanim agenci dotknęli tysięcy plików, projekt rzekomo stworzył szczegółowy przewodnik portowania opisujący, w jaki sposób konstrukcje Zig powinny być wyrażone w Rust. Obejmował on mapowania typów, zasady nazewnictwa, powtarzające się wzorce tłumaczenia, sposób obsługi zależności oraz wyraźne ograniczenia dotyczące tego, co agenci mogli robić.
Cel polega na zastąpieniu niejasnego żądania („napisz ten plik w Rust”) takim, które ma określone ramy: konwertuj plik przy użyciu uzgodnionych mapowań, zachowaj jego widoczne zachowanie w niezmienionej formie i nie ingeruj w architekturę. Agenci radzą sobie znacznie lepiej w takim środowisku, ponieważ zbiór akceptowalnych odpowiedzi jest mały i dobrze określony.
Przewodnik ułatwia również przeglądanie. Zamiast niejasnego pytania „czy to wygląda dobrze?”, recenzent może zadać dwa konkretne pytania:
- Czy ten kod napisany w Rust zachowuje się tak samo jak Zig, który zastępuje?
- Czy agent postępował zgodnie z zasadami tłumaczenia ustalonymi w projekcie?
Oba pytania mają odpowiedzi, które można sprawdzić, co jest dokładnie tym, czego potrzebuje proces recenzji, gdy ilość zmian jest ogromna.
Co zawiera przydatny przewodnik portowania
Jeśli planujesz podobną migrację, nawet w znacznie mniejszym zakresie, taki przewodnik zazwyczaj określa:
- sposób, w jaki każdy typ źródłowy, kontener i mechanizm obsługi błędów mapuje się na język docelowy
- zasady nazewnictwa, aby kod wygenerowany był spójny we wszystkich plikach i przez różne agenty
- kterze zależności mogą zostać wprowadzone, a które są zabronione
- wzorce, których agent nie powinien używać, nawet jeśli umożliwiłyby one skompilowanie pliku
- co stanowi „zakończenie” pracy nad pojedynczym plikiem lub modułem
Lekcja ta ma zastosowanie daleko poza przypadkiem Bun: system instrukcji dla agenta może wpływać na jakość wyniku niemal tak mocno jak wybór modelu. Zespoły, które już posiadają pliki z instrukcjami dla agentów, rozpoznają ten wzorzec; nasz przewodnik pisania skutecznego pliku CLAUDE.md omawia tę samą zasadę w codziennej pracy.
Fałszywie wierny Rust nie zawsze jest idiomatycznym Rustem
Każdy, kto oczekuje, że przetłumaczony kod będzie wyglądać jak projekt napisany od zera w Rust, będzie rozczarowany – i to jest zamierzone. Wierny port powinien przypominać oryginał, z którego został przeniesiony.
To powoduje widoczną kompromisowość. Zig może zwięźle wyrażać określone operacje na niskim poziomie, podczas gdy Rust skłania się ku większej jasności co do typów, czasu trwania, własności oraz granic niebezpiecznego kodu. Dlatego zwięzła funkcja w Zigu może przekształcić się w znacznie dłuższą funkcję w Rust. Ten dodatkowy rozmiar jest skutkiem optymalizacji pod kątem równoważności zachowania, a nie elegancji, i nie jest dowodem na to, że wynik jest błędny.
Pomaga rozróżniać dwie kwestie:
- ">To nie jest ten Rust, który napisałbym od zera."
- ">Ten Rust jest błędny."
Pierwsze to ocena stylu; drugie to wada. Traktowanie ich jako jednego rzeczy prowadzi do nieuzasadnionej krytyki prostych adaptacji.
Najpierw adaptacja, potem optymalizacje
Opisany podejście traktowało tłumaczenie mechaniczne oraz późniejszą optymalizację jako oddzielne etapy. Najpierw sprawić, by Rust zachowywał się jak Bun; następnie udoskonalić sam Rust. Taki podział pracy oznacza, że zespół nie musi jednocześnie rozważać dwóch dużych, wzajemnie oddziałujących zmian. Jeśli podczas czyszczenia pojawi się regresja, można ją przypisać temu procesowi, ponieważ bazowy port został już zweryfikowany pod kątem tych samych testów.
Prawidłowe odczytywanie bloków unsafe
Obserwatorzy, którzy przeglądali ten port, zauważyli dużą liczbę bloków unsafe i potraktowali to jako oznakę niskiej jakości kodu napisanego w Rust. Ta reakcja jest zrozumiała, ale ignoruje to, jak kod w ogóle do tego doszedł.
Rust wymaga, aby operacje potencjalnie niebezpieczne, takie jak odwoływanie się do surowych wskaźników lub wywoływanie funkcji zewnętrznych, były wyraźnie oznaczone jako unsafe. Zig nie posiada odpowiedniego znacznika. Oryginalny kod Bun jest pełen manipulacji pamięcią na niskim poziomie, pracy z wskaźnikami oraz wywołań do komponentów w językach C i C++, więc wierny przekład naturalnie przedstawia dużą część z nich jako unsafe w Rust. Ponadto Bun zawiera JavaScriptCore i komunikuje się z innymi bibliotekami natywnymi, a granica pomiędzy kodem w Rust a C lub C++ z natury wymaga operacji niebezpiecznych.
Innymi słowy, przeniesienie nie dodało zagrożenia; sprawiło jedynie, że istniejące zagrożenia stały się widoczne. Prosta liczba bloków unsafe niewiele mówi o jakości. Bardziej informacyjne pytania to:
- Dlaczego ta konkretna operacja musi być niebezpieczna?
- Czy obszar niebezpieczny jest na tyle mały, by można było o nim rozważać strategie bezpieczeństwa?
To pytania dostarczają również naturalnego planu działania na etapie oczyszczania: należy zmniejszyć obszary niebezpieczne, otoczyć je bezpiecznymi abstrakcjami oraz udokumentować niezmienniki, od których każda z nich zależy.
Gdy tłumaczenie ujawnia możliwości ulepszeń
Bieżące przeniesienie kodu może nadal odkryć możliwości, które zostały ukryte w oryginalnym projekcie. Jeden z przytoczonych przykładów dotyczył ścieżki kodowej, w której implementacja w Zig wykorzystywała C++ do pobierania informacji. W Rust dane lokalne wątku można było uzyskać bezpośrednio, pomijając tę pośrednią wywołanie.
Wniosek nie jest taki, że Rust przewyższa Zig pod względem szybkości; takie uogólnienie nie wytrzymałoby dokładnej analizy. Kwestia jest węższa i bardziej przydatna: przenoszenie kodu między różnymi językami zmusza kogoś lub coś do dokładnego przyjrzenia się każdej ścieżce wywołania, a ta analiza czasami ujawnia prace, które w ogóle nie były konieczne. Takie ustalenia wymagają oceny przypadku po przypadku, a nie włączania ich do debaty na temat języków.
Gdzie skupiły się wysiłki inżynierskie
Migracja nie usunęła pracy inżynierskiej; jedynie ją przeniosła. W tradycyjnym rozwoju duży udział czasu ludzi poświęca się pisaniu kodu implementacyjnego. W rozwoju z wykorzystaniem agentów większa część tego czasu jest przeznaczana na:
- definiowanie ograniczeń i reguł tłumaczenia
- projektowanie ścieżek pracy, w których działają agenci
- pisanie i udoskonalanie testów
- przeglądanie generowanych zmian
- diagnozowanie awarii
Inżynier staje się odpowiedzialny za system, który tworzy i sprawdza kod, a nie za każdą linię samego kodu. W przypadku dużego projektu pętla wygląda mniej więcej tak: specyfikacja, następnie generowanie, kompilacja, testowanie, przegląd, korekta i znowu od początku. Model bierze udział w kilku z tych kroków, ale to projekt pętli decyduje o tym, czy wynik ma jakąkolwiek wartość. To właśnie ten proces pracy, a nie liczba linii kodu, stanowi prawdziwą innowację.
Dlaczego Bun był dobrym kandydatem
Nie każda baza kodu nadaje się do zastosowania tej metody, a Bun miał kilka zalet:
- działająca implementacja służąca jako punkt odniesienia
- znana, stabilna architektura
- zachowanie, które można było zaobserwować za pomocą istniejącego zestawu testów
- język docelowy wyraźnie wybrany
- baza kodu, którą można podzielić na dość niezależne pliki i komponenty
- jedyny, mierzalny cel: utrzymanie programu przy zmianie języka
Porównaj to z prośbą o stworzenie nowego produktu, gdy nie ma referencyjnej implementacji, a to, co jest „poprawne”, zależy od subiektywnej oceny. Ogólną zasadą jest to, że im dokładniej można sformułować pożądane zachowanie, tym większą część implementacji można zautomatyzować. To nie oznacza, że agenci mogą bezpiecznie podejmować się dowolnych dużych projektów. Oznacza to, że automatyzacja działa najlepiej, gdy cel można zmierzyć.
Czego migracja nie dowodzi
Latwo jest przesadzać z takim wynikiem, dlatego warto jasno określić jego ograniczenia. Przykład z Bunem nie pokazuje, że sztuczna inteligencja może zastąpić inżynierów oprogramowania. Nie dowodzi też, że każda baza kodu może zostać w ten sposób przetłumaczona, że generowany kod jest automatycznie łatwy w utrzymaniu lub że Rust zawsze jest szybszy od Zig. Z pewnością nie usprawiedliwia również łączenia ogromnych prośb o pull requesty wygenerowanych przez sztuczną inteligencję z zwykłym kodem produktu.
Wręcz przeciwnie – pokazuje to coś zupełnie innego. Praca polegała na solidnych testach, jasno określonych zasadach, weryfikacji, przeglądzie, debugowaniu oraz ciągłej nadzorze przez ludzi. Analiza opublikowanego procesu pracy wskazuje również, że agenci nie byli pozostawieni sami sobie; ludzie monitorowali proces i dostosowywali go w miarę pojawiania się problemów. Osąd inżynierski nie stracił na znaczeniu – stał się podstawą wszystkiego innego.
Weryfikacja staje się wąskim gardłem
Od dziesięcioleci pisanie kodu uważane jest za powolny proces. Załóżmy, że agent może stworzyć 10 000 linii w czasie, jaki kiedyś potrzebował programista na napisanie 500. Oczywistą reakcją jest celebrowanie tej szybkości, dopóki nie pojawia się kolejne pytanie: kto ma sprawdzić te 10 000 linii?
Tworzenie kodu jest obecnie tanie. Potwierdzenie jego poprawności nadal jest kosztowne, a koszty gwałtownie rosną w przypadku środowisk wykonywania, kompilatorów, baz danych, systemów operacyjnych oraz kodu wymagającego wysokich standardów bezpieczeństwa, gdzie mały błąd może doprowadzić do poważnego awarii. To wskazuje na rosnącą wartość wszystkiego, co służy weryfikacji, a nie tylko generowaniu: zestawów testowych, możliwości obserwacji, analizy statycznej, technik fuzzing, metod formalnych, środowisk izolowanych oraz recenzji przeciwników. Zespoły, które najbardziej skorzystają na agentach do pisania kodu, to prawdopodobnie te z najskuteczniejszymi systemami weryfikacji, a niekoniecznie te z dostępem do najlepszych modeli.
Praktyki do wykorzystania przy własnych zmianach wspomaganych przez AI
Nie potrzebujesz projektu o rozmiarze odpowiadającym czasowi działania, aby zastosować te zasady. Są one równie skuteczne przy przenoszeniu usługi między frameworkami lub konwertowaniu bazy kodu JavaScript na TypeScript.
Zainstaluj testy przed dużą zmianą
Dokładny zestaw testów stanowi wykonalną specyfikację. Im więcej zachowań możesz sprawdzić automatycznie, tym większą część pracy implementacyjnej możesz bezpiecznie przekazać innym. Jeśli stopień pokrycia jest niski, jego wzmocnienie to pierwszy etap migracji, a nie kwestia drugorzędna.
Zapisz zasady
Rozdziel proces generowania od przeglądania
Agent nigdy nie powinien być jedynym osądzającym swojego wyniku. Niezależna procedura sprawdzenia, czy to przez człowieka, drugiego agenta o innych zleceniach, czy obu, dostarcza nowej perspektywy i ujawnia problemy, których nie dostrzega etap generowania.
Pilotaż na małej, reprezentatywnej grupie
Najpierw przeprowadź proces na kilku typowych plikach. Błędne założenia łatwo naprawić, gdy dotyczą dziesięciu plików, ale są bardzo kosztowne, gdy powtórzyły się w dziesięciu tysiącach.
Traktuj generowany kod jako niewiarygodny, dopóki nie zostanie udowodnione jego poprawność
Kod, który się kompiluje, to niekoniecznie kod, który działa. Kompilacja to tylko jedna z wielu kontrol, a nie dowód poprawności.
Zmuszaj ludzi do odpowiedzialności za kluczowe decyzje
Agenci mogą bardzo szybko badać ogromną liczbę możliwych implementacji. Decyzja o tym, co system musi robić i jakie ryzyka są do przyjęcia, pozostaje obowiązkiem człowieka.
Poza debatą o Zig kontra Rust
Kuszące jest przedstawienie tej sytuacji jako konfrontacji Zig z Rust, kodu pisanego ręcznie z AI albo ludzi z agentami. Żadne z tych ujęć nie wyjaśnia zbyt wiele. Port Bun należy lepiej postrzegać jako eksperyment pokazujący, w jaki sposób buduje się oprogramowanie, gdy tworzenie kodu kosztuje prawie nic. Gdy system potrafi przetłumaczyć setki tysięcy linijek kodu, kluczowe pytanie przestaje brzmieć „czy możemy to zaimplementować?” i staje się „czy możemy stworzyć niezawodny proces, który pokaże nam, czy implementacja jest poprawna?”. To zupełnie różne problemy inżynieryjne.
Jak należy ocenić wynik
Pierwszy przekład to jedynie pierwsza faza. Bazy kodu przeniesione mechanicznie mogą z czasem stać się bardziej idiomatyczne: obszary niebezpieczne są sprawdzane i ograniczane, abstrakcje ulepszane, wydajność optymalizowana, a warstwy kompatybilności usuwane. Podobnie jak w przypadku każdego systemu produkcyjnego, o jego skuteczności ostatecznie będzie decydować to, czy służy on prawdziwym użytkownikom. Materiały źródłowe wskazują, że Bun kontynuował wydawanie nowych wersji po migracji; aktualny stan projektu można sprawdzić w jego repozytorium oraz dzienniku zmian.
Prawdziwa ocena będzie wynikać z długoterminowych efektów, a nie z wielkości przepisania kodu:
- czy środowisko wykonawcze pozostaje stabilne w różnych wersjach
- czy kod staje się łatwiejszy do utrzymania
- czy wydajność pozostaje konkurencyjna
- czy błędy związane z pamięcią stają się rzadsze
- czy współpracownicy potrafią zrozumieć i ulepszyć nowy kod
Główne wnioski
- Wartość migracji Bun polega na cyklu reguł, testów, przeglądania i korekty, a nie na ilości generowanego kodu.
- Przedstawienie przepisania jako portowania zachowującego funkcjonalność daje agentom mierzalny cel, a recenzentom konkretny wykaz sprawdzania.
- Przewodnik portowania zmniejsza zakres akceptowalnego wyniku i umożliwia przeglądanie na dużą skalę.
- Rust o dużej objętości lub silnie zależny od elementów niebezpiecznych w wiernym portowaniu jest często naturalną ceną ekwiwalencji; oceniaj kod niebezpieczny pod kątem jego granic, a nie liczby.
- W miarę jak generowanie staje się tańsze, inwestycje w weryfikację będą tym, co odróżni zespoły, a projektowanie tego cyklu zwrotnego informacji staje się coraz ważniejszym elementem pracy inżynierskiej.
Pozycje pokrewne
- Wyspy odnowialne na Bun: Lekcje designu z frameworka Stoneware — Jak framework oparty na serwerze traktuje HTML jako standard, ogranicza działanie JavaScript do obszarów odnowialnych oraz zajmuje się eksportem statycznym, błędami, CSP i wdrażaniem.
- Od celu do zabezpieczonych działań: pętle, weryfikacja i uprawnienia w agentach AI — Dowiedz się, jak agenci AI przekształcają cel w wywołania narzędzi za pomocą pętli plan-act-observe, oraz dlaczego weryfikacja, poziomy autonomii i warstwy uprawnień decydują o ich bezpieczeństwie.