# Goish Rust — Go → Rust Syntax Reference
A single-file conversion table for every Go-like syntax that Goish Rust
supports. Left column is Go; right column is the equivalent Goish Rust.
All examples assume:
```rust
use goish::prelude::*;
```
## Contents
- [1. Primitive types](#1-primitive-types)
- [2. Composite types](#2-composite-types)
- [3. Composite literals](#3-composite-literals)
- [4. `make` / `new` — allocation builtins](#4-make--new--allocation-builtins)
- [5. Built-in functions](#5-built-in-functions)
- [6. Variables, constants, `iota`](#6-variables-constants-iota)
- [7. Control flow](#7-control-flow)
- [8. Functions — multi-return, `_`, variadic](#8-functions--multi-return-_-variadic)
- [9. Type declarations (`type X Y`)](#9-type-declarations-type-x-y)
- [10. Structs — decl + literal + methods](#10-structs--decl--literal--methods)
- [11. Interfaces — Stringer, sort.Interface](#11-interfaces--stringer-sortinterface)
- [12. Errors — `error`, `nil`, `Errorf`, wrap](#12-errors--error-nil-errorf-wrap)
- [13. `fmt` — Print / Sprintf / verbs](#13-fmt--print--sprintf--verbs)
- [14. Goroutines + channels + `select`](#14-goroutines--channels--select)
- [15. `defer` / `recover`](#15-defer--recover)
- [16. Slices — indexing, slicing, `append`, `copy`](#16-slices--indexing-slicing-append-copy)
- [17. Maps](#17-maps)
- [18. Strings / bytes / strconv](#18-strings--bytes--strconv)
- [19. `time`, `sync`, `os`, `io`, `bufio`](#19-time-sync-os-io-bufio)
- [20. Encoding — JSON / base64 / hex / CSV](#20-encoding--json--base64--hex--csv)
- [21. `net/http`, `net/url`, `net/mail`, `net/smtp`, `netip`](#21-nethttp-neturl-netmail-netsmtp-netip)
- [22. Generics-era — `cmp`, `slices`, `maps`, `iter`](#22-generics-era--cmp-slices-maps-iter)
- [23. `crypto`, `hash`, `container/*`, `regexp`, `flag`, `log`, `math/rand`, `unicode`](#23-crypto-hash-container-regexp-flag-log-mathrand-unicode)
- [24. Text toolkit — `html`, `tabwriter`, `scanner`, `template`](#24-text-toolkit--html-tabwriter-scanner-template)
- [25. Testing — `t.Error`, table-driven, benchmarks, `TestMain`](#25-testing--terror-table-driven-benchmarks-testmain)
- [26. Known divergences](#26-known-divergences)
---
## 1. Primitive types
| `int` | `int` | `i64` |
| `int8` | `int8` | `i8` |
| `int16` | `int16` | `i16` |
| `int32` | `int32` | `i32` |
| `int64` | `int64` | `i64` |
| `uint` | `uint` | `u64` |
| `uint8` | `uint8` | `u8` |
| `uint16` | `uint16` | `u16` |
| `uint32` | `uint32` | `u32` |
| `uint64` | `uint64` | `u64` |
| `float32` | `float32` | `f32` |
| `float64` | `float64` | `f64` |
| `byte` | `byte` | `u8` |
| `rune` | `rune` | `i32` |
| `bool` | `bool` | `bool` |
| `string` | `string` | `GoString` newtype |
Go's `int` is platform-sized; Goish pins it to `i64`.
---
## 2. Composite types
| `[]T` | `slice<T>` (Arc-backed, O(1) re-slice) |
| `map[K]V` | `map<K, V>` |
| `chan T` | `Chan<T>` |
| `error` | `error` (newtype; comparable with `nil`) |
| `*T` | `&T` / `&mut T` / `Arc<T>` as appropriate |
| `interface{}` | `&dyn Trait` or concrete generic |
| `[N]T` (array) | `[T; N]` (Rust array) |
---
## 3. Composite literals
| `[]string{"a","b"}` | `slice!([]string{"a", "b"})` |
| `[]int{1,2,3}` | `slice!([]int{1, 2, 3})` |
| `[]uint64{10, 500}` | `slice!([]uint64{10, 500})` *(bare literals widen via `as`)* |
| `[]T{v1, v2}` | `slice!([]T{v1, v2})` *(any type with `From<_>`)* |
| *(typed alt)* | `slice![T; v1, v2]` |
| *(untyped alt)* | `slice![v1, v2, v3]` |
| `map[string]int{"a":1}` | `map!([string]int{"a" => 1})` |
| *(inferred alt)* | `map!{1i64 => "a", 2i64 => "b"}` |
String literals (`&str`) auto-widen to `string`; integer literals widen
to the declared element type via `as`.
---
## 4. `make` / `new` — allocation builtins
| `make([]T, n)` | `make!([]T, n)` |
| `make([]T, 0, cap)` | `make!([]T, 0, cap)` |
| `make([]T, len, cap)` | `make!([]T, len, cap)` |
| `make(map[K]V)` | `make!(map[K]V)` |
| `make(chan T)` | `make!(chan T)` *or* `chan!(T)` |
| `make(chan T, n)` | `make!(chan T, n)` *or* `chan!(T, n)` |
| `new(T)` | `Box::new(T::default())` or `Arc::new(T::default())` |
---
## 5. Built-in functions
| `len(x)` | `len!(x)` |
| `cap(x)` | `cap!(x)` |
| `append(s, x, y)` | `append!(s, x, y)` |
| `copy(dst, src)` | `copy!(dst, src)` |
| `delete(m, k)` | `delete!(m, k)` |
| `close(ch)` | `close!(ch)` |
| `panic("msg")` | `panic!("msg")` |
| `recover()` | `recover!{ body }` *(block form)* |
| `print` / `println` | `Println!(...)` |
`len!` works on `string`, `&str`, `slice<T>`, `map<K,V>`, `Chan<T>`.
`cap!` works on `slice<T>` and `Chan<T>`. Both return Go's `int`.
---
## 6. Variables, constants, `iota`
Go:
```go
var x int = 42
x := 42
var ErrNotFound = errors.New("not found")
const Pi = 3.14
const (
Sunday = iota
Monday
Tuesday
)
const (
_ = iota
KB = 1 << (10 * iota)
MB
GB
)
```
Goish Rust:
```rust
let x: int = 42;
let x = 42i64;
var!(ErrNotFound = errors::New("not found"));
// Call-site: ErrNotFound() — returns `error`
const PI: float64 = 3.14;
Const! {
Sunday = iota;
Monday;
Tuesday;
}
Const! {
_ignored = 1 << (10 * iota);
KB;
MB;
GB;
}
```
`var!` has three forms:
```rust
var!(ErrShortRead = Errorf!("short read")); // error sentinel
var!(DefaultTimeout time::Duration = time::Second * 30i64); // typed lazy
var! { // block form
ErrOne = errors::New("one");
ErrTwo = errors::New("two");
}
```
---
## 7. Control flow
### `if` / `else`
```go
if err != nil { return err }
if x, err := f(); err != nil { ... }
```
```rust
if err != nil { return err; }
let (x, err) = f();
if err != nil { /* ... */ }
```
### `for` loops
| `for i := 0; i < n; i++ { ... }` | `for i in 0..n { ... }` |
| `for i := 1; i <= n; i++ { ... }` | `for i in 1..=n { ... }` |
| `for cond { ... }` | `while cond { ... }` |
| `for { ... }` | `loop { ... }` |
| `for i, v := range xs { ... }` | `for (i, v) in range!(xs) { ... }` |
| `for _, v := range xs { ... }` | `for (_, v) in range!(xs) { ... }` |
| `for k, v := range m { ... }` | `for (k, v) in range!(m) { ... }` |
| `for i, r := range s` *(string)* | `for (i, r) in range!(s) { ... }` |
| `break` / `continue` | `break;` / `continue;` |
### `switch`
Go:
```go
switch x {
case 1, 2: f()
case 3: g()
default: h()
}
// tag-less
switch {
case x > 0: pos()
case x < 0: neg()
}
```
Goish Rust — use Rust's `match`:
```rust
match x {
1 | 2 => f(),
3 => g(),
_ => h(),
}
if x > 0 { pos(); }
else if x < 0 { neg(); }
```
### `select`
```go
select {
case v := <-c: fmt.Println(v)
case c <- 42:
default:
}
```
```rust
select! {
recv(c) |v| => { fmt::Println!(v); },
send(c, 42) => {},
default => {},
}
```
---
## 8. Functions — multi-return, `_`, variadic
```go
func divide(a, b int64) (int64, error) {
if b == 0 { return 0, errors.New("zero") }
return a / b, nil
}
q, err := divide(10, 2)
_, _ = divide(10, 0)
func sum(xs ...int) int { /* ... */ }
sum(1, 2, 3)
sum(xs...)
```
```rust
fn divide(a: int64, b: int64) -> (int64, error) {
if b == 0 { return (0, errors::New("zero")); }
(a / b, nil)
}
let (q, err) = divide(10, 2);
let (_, _) = divide(10, 0);
fn sum(xs: &[int]) -> int { xs.iter().sum() }
sum(&[1, 2, 3]);
sum(&xs);
```
---
## 9. Type declarations (`type X Y`)
Go:
```go
type ID uint64
type IDSlice []ID
type Status string
type Priority int
```
Goish Rust — use `Type!` (dispatches on shape):
```rust
Type!(ID = uint64); // → IntNewtype!(ID = uint64)
Type!(IDSlice = []ID); // → SliceNewtype!(IDSlice = ID)
Enum!(Status); // string enum (const-constructible)
Enum!(Priority = int); // int enum
// Lower-level (also public):
IntNewtype!(ID = uint64);
SliceNewtype!(IDSlice = ID);
```
`IntNewtype!` gives derives + `From<i32/i64/u32/u64/usize>` so
`slice!([]ID{10, 20})` accepts bare literals.
`SliceNewtype!` derefs to `slice<T>`, so all slice methods flow through.
Neither emits `Display` — layer with `stringer!`.
---
## 10. Structs — decl + literal + methods
Go:
```go
type PathTest struct {
path, result string
}
var t = PathTest{"x", "y"}
t2 := PathTest{path: "x", result: "y"}
func (p PathTest) String() string { return p.path + "=" + p.result }
```
Goish Rust:
```rust
Struct!{ type PathTest struct {
path, result string
} }
let t = PathTest!("x", "y"); // positional
let t2 = PathTest { path: "x".into(), result: "y".into() }; // named
stringer! {
impl PathTest {
fn String(&self) -> string {
Sprintf!("%s=%s", self.path, self.result)
}
}
}
```
`Struct!` derives `Clone, Debug, Default, PartialEq, Eq, Hash`.
The companion `PathTest!(...)` macro takes positional fields with
automatic `.into()` on string fields.
### Embedded structs & anonymous fields
Go's embedding doesn't have a 1:1 port; compose explicitly:
```go
type Inner struct { X int }
type Outer struct { Inner; Y int }
```
```rust
Struct!{ type Inner struct { X int } }
Struct!{ type Outer struct { inner: Inner, Y: int } }
// Access: o.inner.X (explicit — no method promotion)
```
---
## 11. Interfaces — Stringer, sort.Interface
### `fmt.Stringer`
```go
type Color struct{ R, G, B int }
func (c Color) String() string {
return fmt.Sprintf("#%02x%02x%02x", c.R, c.G, c.B)
}
```
```rust
Struct!{ type Color struct { R, G, B int } }
stringer! {
impl Color {
fn String(&self) -> string {
Sprintf!("#%02x%02x%02x", self.R, self.G, self.B)
}
}
}
// Now Sprintf!("%s", c), Sprintf!("%v", c), Println!(c) all work.
```
### `sort.Interface`
```go
type ByLen []string
func (s ByLen) Len() int { return len(s) }
func (s ByLen) Less(i, j int) bool { return len(s[i]) < len(s[j]) }
func (s ByLen) Swap(i, j int) { s[i], s[j] = s[j], s[i] }
sort.Sort(ByLen(names))
```
```rust
SliceNewtype!(ByLen = string);
impl sort::Interface for ByLen {
fn Len(&self) -> int { len!(self.0) }
fn Less(&self, i: int, j: int) -> bool {
self.0[i].len() < self.0[j].len()
}
fn Swap(&mut self, i: int, j: int) { self.0.Swap(i, j); }
}
let mut names: ByLen = /* ... */;
sort::Sort(&mut names);
```
---
## 12. Errors — `error`, `nil`, `Errorf`, wrap
```go
err := errors.New("not found")
if err != nil { return err }
wrapped := fmt.Errorf("lookup %s: %w", key, err)
if errors.Is(wrapped, err) { ... }
inner := errors.Unwrap(wrapped)
```
```rust
let err = errors::New("not found");
if err != nil { return err; }
let wrapped = Errorf!("lookup %s: %w", key, err);
if errors::Is(&wrapped, &err) { /* ... */ }
let inner = errors::Unwrap(&wrapped);
```
Key facts:
- `error` is a newtype, not `Option<…>` — `errors::New("…")` returns `error` directly, no `Ok`/`Some`.
- `nil` is a constant `error` value. `err != nil` compiles.
- `Chan<T>`, future `Pointer<T>`, etc. implement `PartialEq<error>` so
`ch != nil` works polymorphically.
- `IsNil` trait provides `.is_nil()` when you need the method form.
Sentinel errors:
```rust
var!(ErrNotFound = errors::New("not found"));
var!(ErrPermission = errors::New("permission denied"));
// Call site: if err == ErrNotFound() { ... }
```
---
## 13. `fmt` — Print / Sprintf / verbs
| `fmt.Println(a, b)` | `fmt::Println!(a, b)` or `Println!(a, b)` |
| `fmt.Printf(fmt, a)` | `fmt::Printf!(fmt, a)` or `Printf!(fmt, a)` |
| `fmt.Sprintf(fmt, a)` | `fmt::Sprintf!(fmt, a)` or `Sprintf!(fmt, a)` |
| `fmt.Fprintf(w, ...)` | `Fprintf!(&mut w, ...)` |
| `fmt.Errorf(...)` | `Errorf!(...)` |
Verb support — takes anything `Display` *or* `Debug` (universal, like Rust's `format!`):
| `%v`, `%+v` | default value (Display; falls back to Debug) |
| `%s` | string / Stringer |
| `%d` | decimal int |
| `%x`, `%X` | hex |
| `%o`, `%b` | octal, binary |
| `%f`, `%e`, `%g` | float |
| `%t` | bool |
| `%c` | rune (character) |
| `%q` | quoted string |
| `%p` | pointer |
| `%w` | wrap error (in `Errorf!` only) |
| Width / precision | `%5d`, `%-8s`, `%.2f`, `%08x` |
---
## 14. Goroutines + channels + `select`
```go
jobs := make(chan int, 4)
go func() {
for i := 1; i <= 3; i++ { jobs <- i }
close(jobs)
}()
for v := range jobs { fmt.Println(v) }
```
```rust
let jobs: Chan<int> = chan!(int, 4);
let g = go!{
for i in 1..=3 { jobs.Send(i); }
close!(jobs);
};
while let (v, true) = jobs.Recv() {
fmt::Println!(v);
}
let _ = g.Wait();
```
| `go f()` | `go!{ f(); }` — returns `Goroutine` |
| `ch <- v` | `ch.Send(v)` |
| `v := <-ch` | `let (v, _) = ch.Recv()` |
| `v, ok := <-ch` | `let (v, ok) = ch.Recv()` |
| `close(ch)` | `close!(ch)` |
| `cap(ch)` / `len(ch)` | `cap!(ch)` / `len!(ch)` |
| `select { ... }` | `select! { ... }` |
Inside `go!{ ... }`, `.Send/.Recv/.Wait` are rewritten to their
`.await` forms at compile time by `goish-macros`.
`select!` arms:
```rust
select! {
recv(ch) |v| => { ... }, // v, ok = <-ch (closed → ok=false)
recv(ch, _) |v, ok| => { ... }, // explicit two-value form
send(ch, expr) => { ... },
default => { ... },
}
```
Nil channel: `Chan::<T>::default()` returns a nil channel — sends and
receives block forever, matching Go semantics.
---
## 15. `defer` / `recover`
```go
func foo() {
defer fmt.Println("cleanup")
defer fmt.Println("inner")
fmt.Println("work")
}
// prints: work, inner, cleanup
```
```rust
fn foo() {
defer!{ fmt::Println!("cleanup"); }
defer!{ fmt::Println!("inner"); }
fmt::Println!("work");
}
```
Recover — Go uses a deferred closure; Goish uses a `recover!` block that
returns `Option<String>`:
```go
defer func() {
if r := recover(); r != nil { log.Printf("panic: %v", r) }
}()
doPanickyThing()
```
```rust
if let Some(r) = recover!{ do_panicky_thing() } {
log::Printf!("panic: %s\n", r);
}
```
---
## 16. Slices — indexing, slicing, `append`, `copy`
Go slice header semantics — Goish `slice<T>` is Arc-backed so re-slicing
is O(1):
| `s[i]` | `s[i as int]` (accepts `int`/`usize`) |
| `s[i] = v` | `s[i as int] = v` |
| `s[i:j]` | `s.Slice(i, j)` |
| `s[i:]` | `s.SliceFrom(i)` |
| `s[:j]` | `s.SliceTo(j)` |
| `s[i:j:k]` | *(deferred — Go's three-index slice)* |
| `len(s)`, `cap(s)` | `len!(s)`, `cap!(s)` |
| `append(s, x)` | `append!(s, x)` |
| `append(s, a...)` | `append!(s, a0, a1, ...)` *(no direct splat; use `.extend`)* |
| `copy(dst, src)` | `copy!(dst, src)` |
| `s[i], s[j] = s[j], s[i]` | `s.Swap(i, j)` |
Arc-backed mutation: a shared slice forks on write via copy-on-write
(`.cow()`). For tight loops you can pre-fork once:
`s.cow(); for ... { s[i] = v; }`.
---
## 17. Maps
| `m := make(map[K]V)` | `let mut m = make!(map[K]V);` or `let mut m: map<K, V> = map::new();` |
| `m[k] = v` | `m[&k] = v` *(insert-on-miss, IndexMut)* |
| `v := m[k]` | `let v = m[&k].clone();` *(zero-value on miss)* |
| `v, ok := m[k]` | `let (v, ok) = m.Get(&k);` |
| `_, ok := m[k]` | `let (_, ok) = m.Get(&k);` |
| `delete(m, k)` | `delete!(m, &k)` |
| `len(m)` | `len!(m)` |
| `for k, v := range m` | `for (k, v) in range!(m) { ... }` |
Missing-key behavior matches Go: `m[k]` returns the zero value of `V`
(any `V: Default`).
---
## 18. Strings / bytes / strconv
```go
strings.Contains(s, "go")
strings.Replace(s, "a", "b", -1)
strings.Split("a,b,c", ",")
strings.ToUpper(s); strings.ToLower(s)
strings.TrimSpace(s)
strings.HasPrefix(s, "go")
strings.HasSuffix(s, "lang")
var buf strings.Builder
buf.WriteString("hello"); buf.WriteRune('!')
s := buf.String()
strconv.Atoi("42")
strconv.Itoa(42)
strconv.FormatFloat(3.14, 'f', 2, 64)
strconv.ParseBool("true")
strconv.Quote("hi\n")
bytes.Equal(a, b)
bytes.NewBuffer(data).String()
```
```rust
strings::Contains(&s, "go");
strings::Replace(&s, "a", "b", -1);
strings::Split("a,b,c", ",");
strings::ToUpper(&s); strings::ToLower(&s);
strings::TrimSpace(&s);
strings::HasPrefix(&s, "go");
strings::HasSuffix(&s, "lang");
let mut buf = strings::Builder::new();
buf.WriteString("hello"); buf.WriteRune('!');
let s = buf.String();
strconv::Atoi("42");
strconv::Itoa(42);
strconv::FormatFloat(3.14, 'f', 2, 64);
strconv::ParseBool("true");
strconv::Quote("hi\n");
bytes::Equal(&a, &b);
bytes::NewBuffer(data).String();
```
String interop helpers:
| `string::from("lit")` | `&str` → `string` |
| `s.as_str()` | `string` → `&str` |
| `cat!(a, b, c)` | Go-shaped `+` concat |
| `string!(...)` | builds `string` from any Display args |
---
## 19. `time`, `sync`, `os`, `io`, `bufio`
### `time`
```go
start := time.Now()
time.Sleep(100 * time.Millisecond)
elapsed := time.Since(start)
t := time.Date(2026, 4, 17, 12, 0, 0, 0, time.UTC)
s := t.Format("2006-01-02")
```
```rust
let start = time::Now();
time::Sleep(time::Millisecond * 100i64);
let elapsed = time::Since(start);
let t = time::Date(2026, 4, 17, 12, 0, 0, 0, time::UTC);
let s = t.Format("2006-01-02");
```
### `sync`
```go
var mu sync.Mutex
mu.Lock(); defer mu.Unlock()
var wg sync.WaitGroup
wg.Add(1); go func() { defer wg.Done(); work() }()
wg.Wait()
var once sync.Once
once.Do(func() { init() })
```
```rust
let mu = sync::Mutex::new(0i64);
{ let mut g = mu.Lock(); *g += 1; } // auto-unlock
let wg = sync::WaitGroup::new();
wg.Add(1);
let w = wg.clone();
go!{ work(); w.Done(); };
wg.Wait();
let once = sync::Once::new();
`sync/atomic` — `AtomicInt64`, `AtomicUint64`, `AtomicBool`, etc. with Go-style
`.Load()`, `.Store()`, `.Add()`, `.CompareAndSwap()` methods.
### `os`, `io`, `bufio`
```go
data, err := os.ReadFile("/etc/hosts")
os.WriteFile("out.txt", data, 0644)
f, _ := os.Open("data.txt")
defer f.Close()
scanner := bufio.NewScanner(f)
for scanner.Scan() { line := scanner.Text() }
os.Getenv("HOME")
os.Setenv("KEY", "val")
os.Exit(0)
```
```rust
let (data, err) = os::ReadFile("/etc/hosts");
os::WriteFile("out.txt", &data, 0o644);
let (f, _) = os::Open("data.txt");
defer!{ f.Close(); }
let mut scanner = bufio::NewScanner(f);
while scanner.Scan() { let line = scanner.Text(); }
os::Getenv("HOME");
os::Setenv("KEY", "val");
os::Exit(0);
```
---
## 20. Encoding — JSON / base64 / hex / CSV
### JSON
```go
data, _ := json.Marshal(map[string]int{"a": 1})
var out map[string]int
json.Unmarshal(data, &out)
```
```rust
let (data, _) = json::Marshal(&map!([string]int{"a" => 1}));
let (out, _) = json::Unmarshal::<HashMap<string, int>>(&data);
```
### base64 / hex / binary
```rust
base64::StdEncoding.EncodeToString(&b);
base64::StdEncoding.DecodeString(&s);
hex::EncodeToString(&b);
hex::DecodeString(&s);
binary::BigEndian.Uint32(&buf);
binary::LittleEndian.PutUint64(&mut buf, x);
```
### CSV
```rust
let mut r = csv::NewReader(strings::NewReader("a,b\n1,2\n"));
let (records, _) = r.ReadAll();
let mut w = csv::NewWriter(&mut os::Stdout());
w.Write(&slice!([]string{"a", "b"}));
w.Flush();
```
---
## 21. `net/http`, `net/url`, `net/mail`, `net/smtp`, `netip`
### HTTP server
```rust
});
log::Fatalf!("%s", http::ListenAndServe(":8080", nil));
```
### HTTP client + context
```rust
let (ctx, _) = context::WithTimeout(context::Background(), 5i64 * time::Second);
let (req, _) = http::NewRequestWithContext(ctx, "GET", url, &[]);
let (resp, err) = http::Do(req);
let (body, _) = io::ReadAll(resp.Body);
```
### Cookies
```rust
let c = Cookie!{
Name: "session",
Value: "abc",
Path: "/",
HttpOnly: true,
SameSite: http::SameSiteLaxMode,
};
let (cookies, _) = http::ParseCookie("a=1; b=2");
```
### URL / netip / mail / smtp
```rust
let (u, _) = url::Parse("https://x.com/p?q=1");
let mut v = url::Values::new();
v.Set("k", "v"); v.Encode();
let (addr, _) = netip::ParseAddr("fe80::1%eth0");
addr.Is6(); addr.Zone();
let (p, _) = netip::ParsePrefix("10.0.0.0/8");
p.Contains(netip::MustParseAddr("10.1.2.3"));
let (a, _) = mail::ParseAddress(r#""Joe Q." <joe@x.com>"#);
a.Name; a.Address;
let (mut c, _) = smtp::Dial("mail.example.com:25");
c.Hello("localhost"); c.Mail("from@x"); c.Rcpt("to@y");
let (mut w, _) = c.Data();
w.Write(b"Subject: hi\r\n\r\nHello."); w.Close();
c.Quit();
```
---
## 22. Generics-era — `cmp`, `slices`, `maps`, `iter`
### `cmp`
```rust
cmp::Compare(1, 2); // -1
cmp::Less(1.5, 2.5); // true
cmp::Or(&[0, 0, 42]); // 42
```
### `slices`
```rust
slices::Contains(&xs, &2);
slices::Index(&names, &"b".to_string());
slices::Sort(&mut xs);
slices::Min(&xs); slices::Max(&xs);
slices::BinarySearch(&sorted, &42);
slices::Insert(&mut xs, 1, &[99, 100]);
slices::Delete(&mut xs, 1, 3);
slices::Compact(&mut xs);
slices::Concat(&[&a, &b, &c]);
slices::Repeat(&xs, 3);
slices::Clone(&xs);
```
### `maps`
```rust
let keys = maps::Keys(&m);
let values = maps::Values(&m);
maps::Equal(&a, &b);
let cloned = maps::Clone(&m);
maps::Copy(&mut dst, &src);
## 23. `crypto`, `hash`, `container/*`, `regexp`, `flag`, `log`, `math/rand`, `unicode`
### `crypto/{md5,sha1,sha256}`
```rust
let mut h = sha256::New();
h.Write(b"hello");
let sum = h.Sum(&[]);
Printf!("%x\n", sum);
let sum = sha256::Sum256(b"hello"); // one-shot
```
### `hash/{crc32, fnv}`
```rust
crc32::ChecksumIEEE(b"abc");
let mut h = fnv::New32a();
h.Sum32();
```
### `container/{list, heap, ring}`
```rust
let mut l = container::list::New::<int>();
l.PushBack(1); l.PushBack(2); l.PushFront(0);
let mut e = l.Front();
while let Some(node) = e { fmt::Println!(node.Value); e = node.Next(); }
let mut h = container::heap::Heap::<int>::new();
h.Push(2); h.Push(1); h.Push(5);
let min = h.Pop();
let r = container::ring::New::<int>(3);
```
### `regexp`
```rust
let re = regexp::MustCompile(r"^\d+$");
re.MatchString("123");
re.FindString("abc 42 xyz");
re.ReplaceAllString("abc 42", "*");
re.FindAllStringSubmatch("a=1 b=2", -1);
```
### `flag` / `log` / `math/rand` / `unicode`
```rust
let port = flag::Int("port", 8080, "listen port");
let name = flag::String("name", "goish", "name");
flag::Parse();
log::Println!("server started");
log::Printf!("port %d\n", 8080);
log::Fatalf!("boom: %s", err);
rand::Seed(42);
rand::Intn(100);
rand::Float64();
unicode::IsLetter('A');
unicode::ToUpper('a');
utf8::RuneCountInString("héllo");
utf8::ValidString("...");
```
---
## 24. Text toolkit — `html`, `tabwriter`, `scanner`, `template`
```rust
html::EscapeString("<a>&\"'");
html::UnescapeString("<a>");
let mut w = tabwriter::NewWriter(&mut os::Stdout(), 0, 8, 1, b'\t', 0);
Fprintf!(&mut w, "name\tage\n"); Fprintf!(&mut w, "alice\t30\n");
w.Flush();
let mut s = scanner::Scanner::new();
s.Init(strings::NewReader("x + 3.14"));
loop { let tok = s.Scan(); if tok == scanner::EOF { break; } }
let (t, _) = template::New("t").Parse("Hi {{.Name}}!");
let mut out = String::new();
t.Execute(&mut out, &serde_json::json!({"Name": "alice"}));
```
---
## 25. Testing — `t.Error`, table-driven, benchmarks, `TestMain`
### Basic test
```go
func TestClean(t *testing.T) {
for _, tc := range tests {
got := path.Clean(tc.path)
if got != tc.want {
t.Errorf("Clean(%q) = %q, want %q", tc.path, got, tc.want)
}
}
}
```
```rust
test!{ fn TestClean(t) {
for tc in &tests() {
let got = path::Clean(&tc.path);
if got != tc.want {
t.Errorf(Sprintf!("Clean(%q) = %q, want %q",
tc.path, got, tc.want));
}
}
}}
```
### Table-driven
```rust
Struct!{ type PathTest struct { path, want string } }
fn tests() -> slice<PathTest> { slice!([]PathTest{
PathTest!("", "."),
PathTest!("abc", "abc"),
})}
```
### Benchmarks
```rust
benchmark!{ fn BenchmarkJoin(b) {
b.ReportAllocs();
while b.Loop() {
path::Join(&slice!([]string{"a", "b"}));
}
}}
```
### `TestMain` / custom harness
```rust
// tests/my_test.rs — in Cargo.toml: harness = false
use goish::prelude::*;
test_h!{ fn TestAddition(t) {
if 2 + 2 != 4 { t.Error("math broke"); }
}}
test_main!{ fn TestMain(m) {
setup();
let code = m.Run();
teardown();
os::Exit(code);
}}
```
### `testing.T` methods
| `t.Error("msg")` | `t.Error("msg");` |
| `t.Errorf(fmt, a)` | `t.Errorf(Sprintf!(fmt, a));` |
| `t.Fatal("msg")` | `t.Fatal("msg");` |
| `t.Fatalf(fmt, a)` | `t.Fatalf(Sprintf!(fmt, a));` |
| `t.Log(...)` | `t.Log(&Sprintf!(...));` |
| `t.Skip("why")` | `t.Skip("why");` |
| `t.Run(name, f)` | `t.Run(name, &mut |t| { ... });` |
| `t.Helper()` | *(no-op; Rust has no Helper machinery)* |
---
## 26. Known divergences
These are spots where full Go semantics can't be expressed in safe Rust,
or where the port deliberately simplifies:
- **Arc-backed slice CoW.** `slice<T>` clones are Arc-shared; mutating a
shared slice forks via `.cow()`. Writes are *not* visible across
previously-shared clones (Go would let the aliased writer mutate the
backing array). Explicit CoW is the safety cost.
- **Three-index slice `s[i:j:k]`.** Deferred — re-slice currently uses
`Slice(i, j)` / `SliceFrom(i)` / `SliceTo(j)`.
- **Embedded structs & method promotion.** No 1:1 port; use explicit
composition and reach through the field.
- **`interface{}` / `any`.** Use concrete generics or `&dyn Trait`; there
is no runtime type-switch on arbitrary `any`.
- **Variadic splat `f(xs...)`.** Rust has no splat. Take `&[T]` and call
`f(&xs)`; for appends, rewrite `append(s, xs...)` as
`{ let mut s = s; s.extend_from_slice(&xs); s }`.
- **Pointers vs references.** Go's `*T` maps to `&T` / `&mut T` /
`Arc<T>` case-by-case; pick what fits ownership.
- **`goto`, labeled break/continue.** Not supported. Factor the loop.
- **`%q` on `[]byte` and a few type-aware verbs.** Deferred.
- **Nil-for-every-type (`NilValue`).** Deferred. `nil` is `error`-typed;
cross-type comparisons work where we've added `PartialEq<error>`
(currently `Chan<T>`; more coming).
---
For a package-by-package port coverage status, see
[PROGRESS.md](PROGRESS.md). For task-oriented recipes, see
[COOKBOOK.md](COOKBOOK.md).