ADT point
Latihan membuat instruksi pembelajaran Abstract Data Type (ADT) point, dengan implementasi bahasa pemrograman Rust.
Mempelajari bagaimana mendesain sebuah Abstract Data Type (ADT) sederhana, Point (titik 2D) dan mengimplementasikannya sebagai modul Rust. Fokus pada pemahaman konsep ADT (enkapsulasi, selektor/konstruktor/operasi/predikat), modulisasi Rust (mod
/pub
), serta konversi dari pola C ke idiom Rust yang aman.
Tujuan Pembelajaran
Setelah mempelajari materi ini Anda akan mampu:
Menjelaskan apa itu ADT dan mengapa enkapsulasi penting.
Mengimplementasikan ADT Point di Rust sebagai modul (file terpisah).
Menyediakan konstruktor, selector (getter), setter, operasi (move), I/O (read/display), dan predikat (
is_origin
).Menggunakan modul dari
main.rs
seperti driver dan menguji perilaku ADT.Menjelaskan perbedaan pemodelan ADT di C vs Rust (keamanan memori, visibilitas, cara pembentukan API).
Prasyarat
Pemahaman dasar Rust:
fn main()
,struct
,impl
, fungsi,mod
,use
.Cara membaca dari
stdin
dan melakukan parsing string → angka.Pengertian dasar tentang float (f32/f64) dan masalah presisi.
Langkah-langkah Pembelajaran
Baca definisi ADT Point: komponen
x
(absis) dany
(ordinat).Implementasikan modul Rust
point
(filesrc/point.rs
) yang mengekspor tipePoint
dan fungsi/metode publik sesuai ADT:Konstruktor:
Point::new(x, y)
Selektor/getter:
x()
dany()
Setter:
set_x()
,set_y()
(opsional untuk immutability/encapsulation)Operasi:
move_by(dx, dy)
→ menghasilkanPoint
baruI/O:
read()
(membaca dua angka dari stdin) dandisplay()
(mencetak(x,y)
)Predikat:
is_origin()
→ cek apakah titik di (0,0) (gunakan toleransi karena floating point)
Buat file
src/main.rs
sebagai driver yang menggunakan modulpoint
untuk menjalankan contoh interaktif (miripmain_point.c
): baca titik, tampilkan, cek origin, bacadx/dy
, tampilkan hasilmove_by
.Jalankan program dan uji dengan berbagai input; observasi edge cases (format input tidak valid, presisi float, dsb).
Desain ADT dalam Rust: Catatan Konseptual
Enkapsulasi: di Rust kita membuat
pub struct Point { ... }
agar tipe dapat digunakan di luar modul, tetapi jangan menandai field (x
,y
) sebagaipub
— cukup sediakan metode publik untuk membaca/menulis. Ini memelihara kontrol atas invariants ADT.Konstruktor:
Point::new(x,y)
membuat instance Point—ini menggantikan prosedurCreatePoint(&p,x,y)
di C.Selektor: metode
x()
dany()
menggantikan macroABSIS(p)
/ORDINAT(p)
ataugetAbsis(p)
.I/O: fungsi
read()
mengembalikanPoint
(ownership), bukan mengisi pointer seperti di C (readPoint(&p)
), karena idiom Rust lebih aman jika ownership dikembalikan.Predikat:
is_origin()
mengembalikanbool
— namun perhatikan masalah perbandingan floating point: gunakan tolerance kecil (eps
) agar kasus seperti 0.0000001 dianggap nol jika sesuai.
Kode: Modul point
(src/point.rs)
Buat file src/point.rs
dengan kode berikut:
// src/point.rs
// Modul Point: implementasi ADT Point dalam Rust.
// Semua method disusun mudah dibaca.
use std::io::{self, Write};
/// Tipe Point: x = absis, y = ordinat
/// Struct dibuat pub agar bisa dipakai dari luar modul,
/// namun field `x` dan `y` dibiarkan private untuk enkapsulasi.
#[derive(Debug, Clone, Copy)]
pub struct Point {
x: f32,
y: f32,
}
impl Point {
/// Konstruktor: buat Point baru
pub fn new(x: f32, y: f32) -> Self {
Point { x, y }
}
/// Getter untuk absis (x)
pub fn x(&self) -> f32 {
self.x
}
/// Getter untuk ordinat (y)
pub fn y(&self) -> f32 {
self.y
}
/// Setter untuk absis
pub fn set_x(&mut self, new_x: f32) {
self.x = new_x;
}
/// Setter untuk ordinat
pub fn set_y(&mut self, new_y: f32) {
self.y = new_y;
}
/// Menentukan apakah point berada di origin (0,0)
/// Karena perbandingan float tidak selalu tepat, gunakan toleransi kecil
pub fn is_origin(&self) -> bool {
let eps = 1e-6_f32;
self.x.abs() < eps && self.y.abs() < eps
}
/// Mengembalikan Point baru hasil penggeseran (tanpa mengubah self)
pub fn move_by(&self, dx: f32, dy: f32) -> Point {
Point::new(self.x + dx, self.y + dy)
}
/// Menampilkan point dalam format "(x,y)" dengan 2 desimal
pub fn display(&self) {
println!("({:.2},{:.2})", self.x, self.y);
}
/// Membaca dua angka float dari stdin untuk membuat Point
/// Implementasi sederhana: jika parsing gagal, mengembalikan Point(0.0, 0.0)
/// Untuk pemula, ini memudahkan; di produksi sebaiknya ditangani lebih ketat.
pub fn read() -> Point {
// Prompt sederhana
print!("Masukkan absis dan ordinat (pisah spasi): ");
let _ = io::stdout().flush();
let mut line = String::new();
if let Err(_) = io::stdin().read_line(&mut line) {
eprintln!("Gagal membaca input, menggunakan (0.0, 0.0)");
return Point::new(0.0, 0.0);
}
let mut parts = line.split_whitespace();
let x_str = parts.next().unwrap_or("0");
let y_str = parts.next().unwrap_or("0");
let x = x_str.parse::<f32>().unwrap_or_else(|_| {
eprintln!("Input untuk absis bukan angka, menggunakan 0.0");
0.0
});
let y = y_str.parse::<f32>().unwrap_or_else(|_| {
eprintln!("Input untuk ordinat bukan angka, menggunakan 0.0");
0.0
});
Point::new(x, y)
}
}
Catatan implementasi:
Fields
x
dany
tidak diberipub
, hanya metode publik yang mengizinkan akses/ubah. Ini menjaga enkapsulasi ADT.read()
menampilkan cara sederhana membaca dua angka; error handling disederhanakan (menggunakan fallback 0.0) untuk menjaga kode tetap ramah pemula. Dalam aplikasi produksi, disarankan meminta input ulang atau mengembalikanResult
.
Kode: Driver / Main (src/main.rs)
Buat file src/main.rs
yang memakai modul point
:
// src/main.rs
mod point; // memuat src/point.rs
use point::Point;
fn main() {
// Test baca dan tulis (dari main_point.c)
let p = Point::read();
print!("Titik yang dibaca ");
p.display();
// Test isOrigin
if p.is_origin() {
println!(" adalah titik origin");
} else {
println!(" bukan titik origin");
}
// Test move
println!(); // baris baru
print!("Geser di arah sumbu x sebesar = ");
let dx = read_float_from_stdin();
print!("Geser di arah sumbu y sebesar = ");
let dy = read_float_from_stdin();
let moved = p.move_by(dx, dy);
print!("Setelah digeser = ");
moved.display();
println!();
}
/// Helper sederhana untuk membaca satu float dari stdin.
/// Jika parsing gagal, mengembalikan 0.0 (sederhana untuk tujuan pembelajaran).
fn read_float_from_stdin() -> f32 {
use std::io::{self, Write};
let _ = io::stdout().flush();
let mut line = String::new();
if let Err(_) = io::stdin().read_line(&mut line) {
eprintln!("Gagal membaca, menggunakan 0.0");
return 0.0;
}
line.trim().parse::<f32>().unwrap_or_else(|_| {
eprintln!("Input bukan angka, menggunakan 0.0");
0.0
})
}
Catatan:
mod point;
memuat filesrc/point.rs
. Alternatif: jika Anda menyusun crate library bisapub mod point;
dilib.rs
dan gunakanuse crate::point::Point
. Untuk tujuan tutorial,mod
dimain.rs
cukup jelas.Input parsing sederhana, fokus pada alur konsep ADT bukan robustness I/O.
Penjelasan Kaitan dengan ADT C
Di C: ADT di-definisikan lewat
struct
di header (point.h
) dan fungsi-fungsi manipulasi dipoint.c
. Selektor sering dibuat macro (ABSIS(p)
) atau fungsiget*
.Di Rust: kita gunakan
struct
+impl
dan metode publik. Rust memberi kontrol visibilitas: struct dapatpub
tapi fields tetap private, sehingga hanya metode yang diekspos (paradigma ADT tetap terjaga).Pola
readPoint(&p)
di C menjadiPoint::read()
yang mengembalikanPoint
(ownership), lebih idiomatik di Rust.
Edge Cases & Pembahasan
Perbandingan floating point (is_origin)
Langsung membandingkan
x == 0.0
sering bermasalah karena representasi float. Contoh diimplementasikan menggunakan toleransieps
(1e-6
). Ini lebih aman untuk aplikasi umum; namun jangan gunakaneps
yang terlalu besar untuk kasus yang membutuhkan presisi tinggi.
Format input tidak valid
Implementasi
read()
menggunakan fallback0.0
saat parsing gagal. Untuk penggunaan nyata, sebaiknya ulangi meminta input atau kembalikanResult<Point, Error>
.
Locale/format bilangan
Parsing
f32
tergantung pada format (titik desimal.
). Jika pengguna memasukkan koma atau format lain, parse akan gagal. Dalam konteks pembelajaran, asumsi input standar (mis.1.2 3.4
) cukup.
Visibility & API stability
Dengan menjaga field private, Anda dapat mengubah representasi internal (mis. menyimpan polar coordinates) tanpa mengubah API publik. Itu adalah keuntungan ADT.
Precision/rounding in display
display()
diatur menampilkan dua desimal; ini hanya format tampilan, bukan menyatakan presisi internal.
Langkah Praktis Menjalankan
Buat project Rust:
cargo new point_adtdemo
Simpan file
src/point.rs
seperti di atas. Gantisrc/main.rs
sesuai contoh driver.cargo run
lalu masukkan contoh input saat diminta, misal:1 2
lalu0.5
dan0.3
.Coba kasus-kasus edge (input non-angka, titik origin
0 0
, dsb.) dan amati output.
Pertanyaan Refleksi, dengan jawaban singkat/clue
Mengapa kita membuat field
x
dany
private? Clue: Untuk menjaga enkapsulasi — agar pengguna ADT hanya berinteraksi lewat API publik; kita bisa mengganti representasi internal tanpa merusak code user. Jawaban singkat: Private fields menjaga invariants ADT dan mencegah pengguna mengubah data secara langsung.Kenapa
Point::read()
mengembalikanPoint
(ownership) bukan menerima&mut Point
seperti di C? Clue: Ownership di Rust mempermudah manajemen memori; mengembalikan nilai memindahkan kepemilikan ke caller. Jawaban singkat: Idiom Rust lebih aman dan sederhana ketika fungsi membuat dan mengembalikan value dibanding memodifikasi pointer caller.Apa risiko membandingkan float dengan tepat (== 0.0)? Clue: Float memiliki representasi biner yang tidak selalu persis. Jawaban singkat: Perbandingan langsung dapat gagal karena rounding error; gunakan toleransi (eps).
Bagaimana cara memperbaiki I/O parsing agar lebih robust? Clue: Gunakan loop meminta input ulang atau kembalikan
Result<Point, Error>
agar pemanggil yang memutuskan tindakan. Jawaban singkat: Implementasi produksi harus menangani error parsing dengan memberi umpan balik pengguna atau mengembalikan error, bukan fallback diam-diam.
Referensi
Sekolah Teknik Elektro dan Informatika, Institut Teknologi Bandung. ADT dalam Bahasa C (materi perkuliahan IF2110/IF2111 — definisi ADT Point dan contoh
point.h
,point.c
,main_point.c
).The Rust Programming Language (The Rust Book) — bab Ownership, Structs, Modules, dan I/O.