1 poin oleh GN⁺ 4 jam lalu | 1 komentar | Bagikan ke WhatsApp
  • Ante adalah desain bahasa sistem yang mencoba memakai fleksibilitas reference counting dan keamanan borrow checking secara bersamaan, sambil menghindari panic runtime ala Rust atau overhead pemeriksaan akses eksklusif ala Swift
  • Mekanisme intinya adalah shape-stability dan temporary uniq conversion, yang memungkinkan pembuatan mutable borrow secara aman pada field dari nilai yang dihitung referensinya, sementara nilai di dalam union hanya diperlakukan sebagai uniq dalam cakupan terbatas
  • Rc<RefCell<T>> di Rust dapat menyebabkan panic saat runtime jika dipakai secara keliru, dan sistem borrowing Swift mencakup pemeriksaan akses eksklusif saat runtime, tetapi Ante mencoba menangani beberapa kasus ini dengan aturan waktu kompilasi
  • Ini masih merupakan desain work-in-progress yang baru sebagian diimplementasikan, dan karena perlu menganalisis tipe secara rekursif untuk menentukan apakah objek tertentu bisa dicapai, penambahan field dapat menjadi breaking API change
  • Pendekatan ini melonggarkan asumsi bahwa shared mutable borrowing selalu mustahil, dan bersama teknik seperti Vale, group borrowing, dan Rust GhostCell, memperluas wilayah pengecualian dalam desain keamanan memori

Gabungan yang ingin dicapai Ante

  • Ante adalah bahasa pemrograman sistem yang menargetkan Rust yang lebih sederhana dengan keamanan memori dan keamanan thread
  • Model dasarnya adalah kepemilikan tunggal dan borrow checking, dengan nilai ditempatkan di stack atau secara inline di dalam struct/array yang menampungnya
  • Saat ingin memprioritaskan kesederhanaan, Anda dapat memilih reference counting dengan menambahkan kata kunci shared pada tipe
  • Dengan shared type Color dan shared type RbTree t, fungsi balance pada red-black tree menjadi sesingkat contoh Python dan lebih kecil daripada contoh C++ maupun Rust
  • Fokus utamanya adalah bagaimana menangani mutable borrow pada data yang dihitung referensinya tanpa risiko panic borrow_mut() ala Rust atau pemeriksaan akses eksklusif runtime ala Swift
  • Ante masih dalam status work-in-progress; sebagian sudah diimplementasikan, sebagian masih teoretis, dan desainnya juga masih berubah

shape-stability dan beberapa referensi mutable

  • shape-stability di Ante adalah konsep bahwa “referensi ke target yang memiliki stable shape akan selalu valid meskipun perubahan apa pun terjadi di tempat lain”
  • Berkat konsep ini, dimungkinkan memiliki beberapa referensi mutable borrow sekaligus terhadap struct yang sama
  • Dalam contoh heal (healer: mut Entity) (target: mut Entity), pemanggilan self_heal yang meneruskan Entity yang sama ke dua argumen dimungkinkan sehingga objek dapat menyembuhkan dirinya sendiri
    • Walaupun healer dan target menunjuk ke Entity yang sama, kode ini tidak dapat menghancurkan Entity tersebut sehingga kedua referensi tetap valid
  • Referensi mutable ke struct itu sendiri, field-nya, dan field dari field tersebut juga dapat diizinkan secara bersamaan
    • Misalnya, ship: mut Spaceship dan engine_alias: mut Engine = ship.engine dapat dipakai bersamaan karena dinilai bahwa ship dan engine di dalamnya tidak akan dihancurkan selama fungsi berjalan
  • Rust dan Swift tidak mengizinkan beberapa referensi &mut sekaligus menunjuk ke data yang sama

Mutable borrow pada field dari nilai yang dihitung referensinya

  • Di Ante, jika shared ditambahkan di depan definisi tipe, tipe itu akan otomatis memakai reference counting
  • Dalam contoh shared mut type Spaceship, launch memegang Spaceship yang setara dengan Rc sambil meneruskan mut ship.engine ke set_fuel
  • Karena launch mempertahankan objek induk Spaceship, dapat disimpulkan bahwa field engine di dalamnya juga tetap hidup
  • Aturan umumnya adalah bahwa referensi borrow mut selalu dapat dibuat untuk field dari tipe shared mut
    • Namun, ini tidak berarti mutable borrow selalu bisa dibuat untuk semua target di dalam field tersebut; aturan tambahan tetap diperlukan
  • Contoh berikutnya memakai notasi Rc Spaceship yang lebih eksplisit alih-alih sugar syntax shared mut type Spaceship
    • shared mut type Spaceship menjadi type Spaceship, dan var ship: Spaceship menjadi var ship: Rc Spaceship

Titik di mana union menimbulkan masalah keamanan

  • Union menyimpan isinya secara inline sehingga mengurangi pointer chasing dan cache miss, yang menguntungkan untuk kecepatan
    • Jika union Engine di C berada di dalam struct Spaceship, maka StringTheoryEngine dan ImpulseEngine berada langsung di memori Spaceship
    • Ini kontras dengan pendekatan seperti Java yang memakai interface dan pointer
  • Masalahnya adalah union sulit didukung secara aman dalam bahasa yang aman terhadap memori
  • Dalam contoh Engine yang dapat berupa StringTheoryEngine(str: String) atau ImpulseEngine(fuel: I32), segfault dapat terjadi saat ship dan other_ship menunjuk ke Spaceship yang sama
    • setelah mengambil referensi internal string melalui match uniq ship.engine
    • lalu mengganti engine yang sama ke varian lain dengan other_ship.engine := ImpulseEngine 0x42
    • kemudian memodifikasi str lama, yang berarti memakai bagian dalam setelah kontainernya dihancurkan
  • Karena itu, Ante perlu melarang pembuatan referensi mutable borrow ke salah satu varian ketika referensi mutable borrow tersebut menunjuk ke union
  • Ini berkebalikan dengan aturan untuk struct
    • Jika ada referensi mut ke struct, referensi mut ke field-nya bisa dibuat
    • Jika ada referensi mut ke union, referensi mut ke bagian dalam variannya tidak bisa dibuat

uniq dan temporary uniq conversion

  • uniq berarti exclusive mutable reference, yaitu referensi mutable eksklusif
  • Jika suatu variabel menyimpan uniq Spaceship, itu adalah satu-satunya referensi yang dapat dipakai untuk Spaceship tersebut
    • Konsep ini mirip dengan &mut Spaceship di Rust
  • Untuk menangani bagian dalam union secara aman, Ante memakai temporary uniq conversion
  • Aturan intinya adalah bahwa jika referensi lain yang berpotensi menjadi alias tidak dipakai dalam cakupan tertentu, maka referensi uniq dapat diperoleh secara sementara
    • Dalam blok match uniq ship.engine, akses ke ship.engine diperlakukan seperti uniq
    • Selama blok ini, kompiler melarang penggunaan variabel lain yang sudah ada jika variabel itu dapat secara tidak langsung memuat Spaceship
  • Rust mencegah keberadaan uniq karena “referensi lain mungkin ada di suatu tempat”, sedangkan Ante mengizinkan uniq selama referensi-referensi itu tidak digunakan dalam cakupan tersebut
  • Dalam konteks ini, uniq Spaceship bukan benar-benar referensi yang unik secara global, melainkan satu-satunya referensi yang dapat digunakan dalam cakupan itu
    • Nuansanya mirip pointer restrict di C

Akses yang diizinkan dan yang ditolak

  • Jika other_ship: Rc Spaceship diakses di dalam cakupan match uniq ship.engine, seharusnya terjadi error kompilasi
    • karena other_ship.engine bisa menjadi alias dari ship.engine
    • dan perubahan pada other_ship.engine saat ship.engine sedang dipakai dapat memicu drop
  • Struct lain seperti HasAShip yang memiliki field Rc Spaceship juga ditolak dengan alasan yang sama
    • other.ship.engine juga dapat mencapai Spaceship yang sama secara tidak langsung
  • Sebaliknya, bilangan bulat seperti new_fuel: I32 tetap dapat dipakai
    • karena I32 tidak mungkin memuat referensi ke Spaceship
  • Jika Spaceship sendiri memiliki field seperti follow_ship: Rc Spaceship, maka kasus itu juga ditolak
    • Dalam situasi itu, uniq Spaceship dapat dicapai lagi melalui jalur internalnya sendiri, sehingga secara umum tipe rekursif tidak dapat menjalani konversi mut -> uniq

Batasan pada pemanggilan dan pengembalian fungsi

  • Konversi mut -> uniq juga dapat terjadi saat pemanggilan fungsi
  • Ketika foo (var ship: Rc Spaceship) (new_res: Resonator) memanggil maybe_use_resonator ship new_res, ship dikonversi menjadi uniq Spaceship di titik pemanggilan
    • Kompiler hanya perlu memeriksa apakah argumen lain dapat memuat referensi ke Spaceship
    • Dalam contoh itu, Resonator tidak memuat referensi semacam itu sehingga diizinkan
  • Pada pengembalian nilai, referensi uniq hasil konversi tidak dapat dikembalikan sebagai uniq biasa
    • karena setelah fungsi mengembalikan, pemeriksaan kompiler bahwa “variabel yang bisa menjadi alias tidak dipakai dalam cakupan ini” tidak lagi berlaku
  • Sebagai gantinya, tipe pengembalian dapat dinyatakan sebagai local uniq Foo
    • Secara internal, saat mengonversi mut ref ke uniq ref, yang sebenarnya selalu dibuat adalah local uniq
    • Dalam kebanyakan kasus ini dapat dipakai seperti uniq biasa, tetapi saat mengembalikannya perlu dinyatakan secara eksplisit

Biaya desain dan alternatifnya

  • Ante dapat mengubah referensi yang dihitung referensinya seperti Rc Spaceship menjadi uniq Spaceship sementara tanpa error runtime
  • Kekurangannya, kompiler harus menelusuri tipe secara rekursif untuk menjawab pertanyaan seperti “apakah Engine bisa mencapai Spaceship?”
  • Analisis seperti ini bisa rapuh
    • menambahkan field pada struct bisa menjadi breaking API change
  • Pembuat Ante, Jake, sedang mencari cara yang lebih baik untuk mempertahankan jaminan ini
    • pendekatan seperti group borrowing dan Flix references, yaitu memberi tiap tipe mutable bersama semacam tipe brand anonim yang unik
    • pendekatan menambahkan effect seperti Mutates 'a saat tipe bersama diubah agar analisis tipe tidak diperlukan
    • pendekatan di mana pengguna memeriksa saat runtime apakah dua referensi menunjuk ke objek yang berbeda, atau menyediakan pemeriksaan unsafe yang dibungkus dalam API aman
    • pendekatan di mana kompiler melacak nilai yang tidak disimpan secara tidak langsung di dalam Rc sehingga tidak mungkin menjadi alias
  • Gagasan yang mirip dengan iso permission milik Pony, atau izin sementara yang melihat ke dalam struct tetapi melarang penggunaan referensi yang menunjuk ke luar, juga masih menjadi kemungkinan
  • Bagian sulitnya adalah mempertahankan fleksibilitas ini sambil tetap menjaga target Ante yaitu kegunaan, keterbacaan, dan kesederhanaan

Arus yang lebih luas dalam keamanan memori

  • Shared mutable borrowing dulu dianggap mustahil, dan sudut pandang ini menjadi salah satu landasan desain Rust
  • Kini berbagai pengecualian mulai menumpuk
    • Ante dapat memperoleh referensi borrow uniq dari data shared-mutable melalui aturan local uniqueness
    • Vale dapat memperoleh referensi borrow immutable dari data shared-mutable melalui pure function
    • group borrowing dapat membuat referensi borrow shared-mutable bahkan jika tidak shape-stable
    • GhostCell di Rust memungkinkan graph objek saling menunjuk dengan bebas, tetapi pada saat tertentu hanya boleh ada satu referensi mutable ke salah satunya
  • Arus ini memberi isyarat bahwa mungkin ada prinsip yang lebih umum untuk menangani shared mutable borrowing dalam desain keamanan memori

Perbandingan dengan Rust Cell

  • Pengguna Rust mungkin bertanya apa bedanya dengan pendekatan menaruh Cell pada field struct
  • Dalam contoh Ante, dari Rc Spaceship dapat diperoleh referensi mut String ke status: String lalu langsung menambahkan " (refueling)"
  • Dengan pendekatan Cell<String> di Rust, &mut String tidak bisa diperoleh dari Rc<Spaceship>
    • Sebagai gantinya, Anda harus memasukkan nilai default sementara dengan status_ref.replace(String::new())
    • memodifikasi String yang dikeluarkan
    • lalu mengembalikannya lagi di akhir dengan replace(status)
  • Pendekatan ini memiliki beberapa kelemahan
    • perlu membuat instance default seperti ""
    • ada risiko lupa memanggil replace yang terakhir
    • ada risiko seseorang membaca status saat nilainya masih dalam keadaan terganti
  • Ante memungkinkan memperoleh referensi sementara ke string status, dan selama itu kompiler memastikan kode lain tidak dapat mengaksesnya

1 komentar

 
GN⁺ 4 jam lalu
Komentar di Lobste.rs
  • Menganggap bahwa “peminjaman mutable bersama” itu mustahil bukan sekadar pengorbanan yang diterima Rust demi mencapai tujuannya, melainkan lebih dekat dengan tujuan inti Rust itu sendiri
    Sebab state mutable bersama membuat penalaran lokal terhadap kode menjadi sulit
    "References are like jumps" by withoutboats membahas hal ini dengan baik. Argumennya adalah bahwa mencegah perubahan tak disengaja pada state yang memiliki alias merupakan inti untuk membuat sistem yang bekerja dengan benar menjadi lebih mudah, dan aturan lifetime Rust bukan sekadar mekanisme untuk menghindari garbage collection, melainkan struktur yang lebih mendalam untuk memastikan kemampuan bernalar dalam bahasa yang mengizinkan state mutable dan state beralias secara bersamaan

    • Dulu saat penulis membahas Mojo borrow checker, saya juga merasa hal yang sama. Borrow checker Rust mempertahankan semantik nilai bahkan pada program single-thread
  • Kelihatannya cukup bagus
    Jika pemahaman saya benar, keajaiban berpindah dari referensi bersama ke referensi mutable dimungkinkan karena terbatas pada tipe yang tidak dibagikan antar-thread, dan keunikan Rc tampaknya dijamin dengan memperlakukan semua objek dari tipe yang sama seolah-olah dipinjam dengan lifetime yang sama
    Mana yang lebih baik antara sintaks eksplisit dan sintaks alami mungkin soal selera, tetapi ini menunjukkan bahwa jika compiler mengetahui lebih banyak tentang Cell, ia dapat mengizinkan referensi mutable terhadapnya dengan lebih fleksibel
    Dan ini juga menghindari istilah membingungkan seperti di Rust, ketika mut dipakai seolah-olah berarti bukan mutable, melainkan eksklusif/unik

    • Saya sempat penasaran bagaimana jadinya antar-thread. Pertanyaannya kira-kira seperti “apakah promosi uniq berarti memperoleh lock?”, tetapi saya memahaminya sebagai bahwa pembandingnya bukan Arc, melainkan Rc
    • Bisa jelaskan lebih jauh bagian bahwa mut berarti eksklusif/unik?
  • Saya penasaran apakah ada yang bisa menebak apa prinsip pemersatu yang diisyaratkan di bagian akhir

  • Diskusi-diskusi sebelumnya tentang tulisan blog antelang.org juga layak dibaca

  • Saya belum begitu paham cara kerjanya. Sepertinya maksudnya “jika punya pointer mutable ke sebuah objek, kita bisa memperoleh referensi perubahan ke slice dari objek itu”
    Namun kalau begitu, misalnya tampaknya hal seperti mutref someobjext = …, mutref subfield = someobjext.a.b, someobjext.a = somethingelse bisa dilakukan, dan itu bisa membuat subfield menjadi tidak valid atau rusak karena nilainya berubah
    Di tulisannya ada banyak penjelasan, perbandingan dengan bahasa lain, dan contoh kode, tetapi saya sulit menemukan bagian yang benar-benar merangkum semantik langkah demi langkah dari perilaku ini secara mendasar