Paket dan import

Deklarasi package, import, dan aturan lingkupnya.

Daftar isi

Odin mengorganisasi kode ke dalam package, yaitu unit berupa satu direktori yang memuat satu atau beberapa berkas sumber dengan deklarasi package yang sama. Topik ini membahas cara mendeklarasikan package, bentuk pernyataan import untuk pustaka bawaan maupun package lokal, peran library collection dalam pencarian package, aturan visibilitas entitas antarpackage, serta penyusunan proyek multi-berkas yang dijalankan dengan satu perintah. Seluruh contoh disusun menjadi satu proyek kecil bernama math_tools yang dijalankan utuh, dan setiap keluaran yang ditampilkan adalah keluaran asli dari eksekusinya.

Satu direktori, satu package#

Package di Odin terikat pada direktorinya. Seluruh berkas .odin di dalam satu direktori wajib mendeklarasikan nama package yang sama pada baris pertama, dan satu direktori tidak boleh memuat dua deklarasi package yang berbeda. Compiler menolak direktori yang melanggar aturan ini dengan pesan error sebelum program sempat dijalankan. Package yang dapat dieksekusi wajib memuat proc main sebagai titik awal eksekusinya, dan proc tersebut biasanya berada pada package utama proyek.

Berkas-berkas dalam satu direktori membentuk satu lingkup bersama. Entitas yang dideklarasikan pada satu berkas dapat dipanggil dari berkas lain dalam direktori yang sama tanpa pernyataan import, karena keduanya sudah menjadi anggota package yang sama. Proyek contoh pada topik ini memanfaatkan sifat tersebut lewat berkas konversi.odin berikut, yang mendeklarasikan proc ke_celsius untuk mengonversi suhu Fahrenheit ke Celsius.

odin konversi.odin
package main

ke_celsius :: proc(fahrenheit: f64) -> f64 {
        return (fahrenheit - 32) * 5 / 9
}

Pada berkas main.odin yang diperlihatkan pada bagian proyek, proc ke_celsius dipanggil tanpa awalan apa pun. Pemanggilan tanpa awalan adalah tanda bahwa kedua berkas berada dalam package yang sama, sedangkan entitas dari package lain selalu ditulis dengan awalan nama importnya. Pembedaan ini membuat asal setiap pemanggilan terbaca langsung dari kodenya.

odin Contoh: pemanggilan antarberkas dalam satu package
// tidak-dijalankan
// ke_celsius dideklarasikan di konversi.odin, satu package dengan main.odin
f.printfln("77 F dalam Celsius : %.2f", ke_celsius(77))

Bentuk import dan koleksi pustaka#

Pernyataan import membuka akses ke package lain pada lingkup berkas yang menuliskannya. Awalan bernama seperti core: menyatakan library collection, yaitu koleksi pustaka yang menjadi tempat compiler mencari package. Odin menyediakan tiga koleksi bawaan. Koleksi core berisi pustaka standar, koleksi base berisi runtime beserta intrinsik compiler, sedangkan koleksi vendor berisi pustaka pihak ketiga yang dibundel bersama compiler, seperti raylib dan glfw.

Tanpa awalan, jalur import dicari relatif terhadap berkas yang mengimpor. Bentuk inilah yang dipakai untuk package lokal, yaitu subdirektori di dalam proyek sendiri. Nama package hasil import dapat diganti dengan alias, sehingga pemanggilannya lebih pendek atau lebih jelas. Bentuk import f "core:fmt" menyimpan seluruh isi core:fmt di bawah nama f, sedangkan tanpa alias, nama awalannya diambil dari elemen terakhir jalur import.

odin Contoh: bentuk pernyataan import
// tidak-dijalankan
import "core:fmt"       // koleksi bawaan core
import "base:runtime"   // koleksi bawaan base
import "vendor:raylib"  // koleksi bawaan vendor
import f "core:fmt"     // alias f untuk core:fmt
import "util"           // tanpa awalan: relatif terhadap berkas yang mengimpor
import "ekstra:geo"     // koleksi kustom yang didaftarkan lewat -collection

Koleksi baru dapat didaftarkan lewat opsi -collection:nama=jalur pada perintah odin run atau odin build. Opsi ini memetakan sebuah nama koleksi ke satu direktori, lalu setiap subdirektori di dalamnya dapat diimpor dengan awalan nama koleksi tersebut. Jalur pada opsi ini dituliskan relatif terhadap direktori kerja saat perintah dijalankan. Proyek contoh pada topik ini mendaftarkan koleksi bernama ekstra yang menunjuk direktori ekstra, sehingga import "ekstra:geo" menyelesaikan jalurnya ke subdirektori ekstra/geo.

Proyek contoh math_tools#

Seluruh konsep di atas dipakai oleh satu proyek kecil bernama math_tools. Proyek ini menghitung beberapa nilai aritmetika, konversi suhu, dan luas persegi dengan membagi pekerjaannya ke dalam tiga package. Struktur direktorinya tampak seperti berikut.

text Struktur proyek math_tools
math_tools/
├── main.odin
├── konversi.odin
├── util/
│   └── util.odin
└── ekstra/
    └── geo/
        └── geo.odin

Berkas main.odin menjadi package main sekaligus tempat ketiga bentuk import dipakai bersama. Pustaka core:fmt diimpor dengan alias f, package lokal util diimpor tanpa awalan, sedangkan package geo diimpor melalui koleksi kustom ekstra. Proc ke_celsius dari konversi.odin dipanggil tanpa awalan karena berada pada satu package dengan main.odin.

odin main.odin
package main

import f "core:fmt"
import "util"
import "ekstra:geo"

main :: proc() {
        f.println("jumlah 3 + 4       :", util.jumlah(3, 4))
        f.println("Kali 3 x 4         :", util.Kali(3, 4))
        f.printfln("77 F dalam Celsius : %.2f", ke_celsius(77))
        f.printfln("luas persegi sisi 5: %v", geo.Luas_Persegi(5))
}

Berkas util/util.odin mendefinisikan dua proc aritmetika beserta satu proc pembantu yang bersifat privat. Peran atribut privat pada proc pembantu tersebut dibahas pada bagian visibilitas.

odin util/util.odin
package util

jumlah :: proc(a, b: int) -> int {
        return a + b
}

Kali :: proc(a, b: int) -> int {
        return a * b
}

@(private)
bantu_dua_kali :: proc(x: int) -> int {
        return x * 2
}

Berkas ekstra/geo/geo.odin berada di luar mekanisme import relatif karena hanya dapat dicapai melalui koleksi kustom. Isinya satu proc geometri dasar yang menghitung luas persegi dari panjang sisinya.

odin ekstra/geo/geo.odin
package geo

Luas_Persegi :: proc(sisi: f64) -> f64 {
        return sisi * sisi
}

Perintah odin run . mengompilasi seluruh berkas .odin pada tingkat atas direktori kerja sebagai satu package, lalu menyelesaikan setiap import yang dituliskan di dalamnya. Berkas lain seperti index.md diabaikan, dan subdirektori tidak ikut dikompilasi kecuali diimpor secara eksplisit. Karena main.odin mengimpor ekstra:geo, perintah ini wajib disertai opsi pendaftaran koleksinya.

bash
# mengompilasi dan menjalankan proyek dari direktori kerja
odin run . -collection:ekstra=ekstra
text Keluaran
jumlah 3 + 4       : 7
Kali 3 x 4         : 12
77 F dalam Celsius : 25.00
luas persegi sisi 5: 25

Empat baris keluaran berasal dari tiga package berbeda. Baris pertama dan kedua memanggil proc dari package util, baris ketiga memanggil ke_celsius dari konversi.odin yang satu package dengan main.odin sehingga dipanggil tanpa awalan, sedangkan baris keempat memanggil proc dari package geo melalui koleksi ekstra. Satu perintah sudah merangkai semuanya, dan tidak ada berkas binari yang tertinggal di direktori proyek karena odin run mengompilasi program ke lokasi sementara.

Visibilitas entitas antarpackage#

Seluruh deklarasi dalam sebuah package bersifat publik secara default dan dapat dipanggil dari package mana pun yang mengimpornya. Huruf kapital pada awal nama tidak berperan dalam penentuan ini. Pada keluaran proyek math_tools, proc jumlah dengan huruf kecil dan proc Kali dengan huruf kapital sama-sama dapat dipanggil dari main.odin tanpa perbedaan perlakuan.

Penutupan akses dilakukan secara eksplisit melalui atribut @(private). Entitas yang diberi atribut ini hanya dapat dipakai di dalam package miliknya, dan compiler menolak pemanggilan dari luar dengan pesan error. Varian @(private="file") lebih ketat karena entitasnya hanya dapat dipakai di dalam berkas yang sama, walaupun berkas lain masih berada dalam satu package. Bentuk @(private) tanpa nilai setara dengan @(private="package").

Berkas util/util.odin pada proyek math_tools memuat contohnya, yaitu proc bantu_dua_kali yang diberi atribut @(private). Proc tersebut sengaja tidak dipanggil oleh main.odin karena memang tidak diperuntukkan bagi package lain. Demi melihat perilaku compiler ketika aturan ini dilanggar, proyek uji kecil berikut dibuat, dijalankan, lalu dihapus.

bash
# membuat proyek uji dengan satu entitas privat di package vis
mkdir -p uji-vis/vis

cat > uji-vis/vis/vis.odin <<'EOF'
package vis

angka :: proc(x: int) -> int {
        return x + 1
}

@(private)
angka_rahasia :: proc(x: int) -> int {
        return x * 100
}
EOF

# main mencoba memanggil entitas privat dari package vis
cat > uji-vis/main.odin <<'EOF'
package main

import f "core:fmt"
import "vis"

main :: proc() {
        f.println(vis.angka_rahasia(5))
}
EOF

# menjalankan proyek uji, lalu menghapusnya
odin run uji-vis
rm -r uji-vis
text Keluaran
/home/kal/my-project/content/odin/paket-dan-pustaka/paket-dan-import/uji-vis/main.odin(7:19) Error: 'angka_rahasia' is not exported by 'vis' 
	f.println(vis.angka_rahasia(5)) 
	          ^~~~~~~~~~~~~~~~^ 

Pesan 'angka_rahasia' is not exported by 'vis' menyebut dengan tepat entitas mana yang dilarang beserta package asalnya. Perbaikannya memiliki dua arah, yaitu menghapus atribut @(private) bila entitas memang dimaksudkan untuk publik, atau menghapus pemanggilannya bila entitas memang untuk pemakaian internal package.

Kesalahan umum#

Tiga kesalahan berikut paling sering muncul ketika proyek mulai dibagi ke dalam beberapa package. Masing-masing direproduksi bersama pesan error aslinya, kemudian diikuti penyebab dan perbaikannya.

Koleksi kustom lupa didaftarkan#

Error ini muncul ketika proyek math_tools dijalankan tanpa opsi -collection:ekstra=ekstra. Compiler membaca import "ekstra:geo", mencari koleksi bernama ekstra, dan tidak menemukannya karena pendaftaran koleksi hanya melekat pada baris perintah yang memuat opsinya.

bash
# menjalankan proyek tanpa mendaftarkan koleksi kustomnya
odin run .
text Keluaran
/home/kal/my-project/content/odin/paket-dan-pustaka/paket-dan-import/main.odin(5:1) Syntax Error: Unknown library collection: 'ekstra' 
	import "ekstra:geo" 
	^~~~~~~~~~~~~~~~~~^ 

Perbaikannya adalah menyertakan -collection:ekstra=ekstra pada setiap perintah odin run maupun odin build yang mengompilasi proyek ini. Pendaftaran koleksi tidak tersimpan di berkas mana pun, sehingga setiap pemanggilan perintah harus memuat opsinya sendiri.

Dua deklarasi package dalam satu direktori#

Ketidakcocokan antara nama folder dan nama package tidak memicu error, sebagaimana dijelaskan pada catatan di bagian awal. Yang benar-benar ditolak compiler adalah kehadiran dua deklarasi package berbeda dalam satu direktori, misalnya satu berkas dengan package main dan berkas lain dengan package lain. Compiler menolak kombinasi itu dengan pesan error sintaks Different package name beserta saran memakai flag -file bila memang maksudnya adalah mode berkas tunggal, dan build tidak berlanjut sampai salah satu deklarasinya diubah.

Pesan error menunjuk berkas yang menyimpang beserta nama package yang diharapkan compiler, yaitu main. Perbaikannya adalah menyamakan deklarasi package pada kedua berkas karena keduanya memang harus membentuk satu package yang sama. Saran -file pada pesan tersebut hanya relevan bila berkas yang dimaksud berdiri sendiri sebagai package satu berkas, bukan bila keduanya memang satu proyek.

Import melingkar#

Dua package yang saling mengimpor membentuk siklus, dan compiler menolaknya secara eksplisit. Proyek uji berikut memuat package num yang mengimpor package txt, sedangkan txt juga mengimpor num.

bash
# membuat dua package yang saling mengimpor
mkdir -p siklus/num siklus/txt

cat > siklus/num/num.odin <<'EOF'
package num

import "../txt"

angka :: proc() -> string {
        return txt.gaya()
}
EOF

cat > siklus/txt/txt.odin <<'EOF'
package txt

import "../num"

teks :: proc() -> string {
        return num.angka()
}

gaya :: proc() -> string {
        return "[angka]"
}
EOF

cat > siklus/main.odin <<'EOF'
package main

import "core:fmt"
import "num"

main :: proc() {
        fmt.println(num.angka())
}
EOF

# menjalankan proyek uji, lalu menghapusnya
odin run siklus
rm -r siklus
text Keluaran
/home/kal/my-project/content/odin/paket-dan-pustaka/paket-dan-import/siklus/num/num.odin(3:1) Error: Cyclic importation of 'txt' 
	import "../txt" 
	^~~~~~~~~~~~~~^ 
/home/kal/my-project/content/odin/paket-dan-pustaka/paket-dan-import/siklus/txt/txt.odin(3:1) Error: 'num' refers to 
	import "../num" 
	^~~~~~~~~~~~~~^ 

Pesan Cyclic importation of 'txt' adalah akar masalahnya, sedangkan pesan kedua pada siklus/txt/txt.odin adalah akibat dari siklus yang sama. Perbaikannya adalah memutus siklus tersebut, misalnya dengan memindahkan entitas yang sama-sama dibutuhkan ke package ketiga yang diimpor keduanya, sehingga arah import menjadi satu garis lurus.

Ringkasan#

  • Satu direktori adalah satu package, dan seluruh berkas .odin di dalamnya wajib mendeklarasikan nama package yang sama.
  • Import dengan awalan menunjuk koleksi pustaka, yaitu core, base, vendor, atau koleksi kustom yang didaftarkan lewat -collection:nama=jalur, sedangkan import tanpa awalan dicari relatif terhadap berkas yang mengimpornya.
  • Deklarasi bersifat publik secara default, dan @(private) atau @(private="file") menutup akses dari luar package atau dari luar berkas, tanpa memandang huruf kapital pada awal namanya.
  • Compiler menolak dua deklarasi package dalam satu direktori dan import melingkar dengan pesan error yang menunjuk lokasinya secara eksplisit.

Referensi#