goish
Write Rust using Go idioms.
goish is a Rust crate that ports Go's standard library and built-in syntax
constructs into Rust so that Go programmers can write Rust code that reads and
feels like Go — (val, err) := fn(), if err != nil, defer f.Close(),
go func() { ... }(), for i, v := range slice, []string{"a","b"} — while
still getting Rust's safety and performance under the hood.
use *;
Install
Add to Cargo.toml:
[]
= { = "https://github.com/chanwit/goish" }
Or, from crates.io:
[]
= "0.11" # latest
Then in every file where you want Go-shaped code:
use *;
Cheat-sheet
Types
| Go | goish |
|---|---|
int / int64 / float64 / byte / rune |
int / int64 / float64 / byte / rune |
string |
string (alias of std::string::String) |
[]T |
slice<T> (alias of Vec<T>) |
map[K]V |
map<K, V> (alias of HashMap<K, V>) |
chan T |
Chan<T> |
error |
error (a newtype; nil is a zero-value constant) |
Composite literals
| Go | goish |
|---|---|
[]string{"a","b","c"} |
slice!([]string{"a","b","c"}) |
[]int{1,2,3} |
slice!([]int{1,2,3}) |
map[string]int{"a":1,"b":2} |
map!([string]int{"a" => 1, "b" => 2}) |
make([]int, 5) |
make!([]int, 5) |
make([]T, 0, n) |
make!([]T, 0, n) |
make(map[K]V) |
make!(map[K]V) |
make(chan int, 10) |
make!(chan int, 10) or chan!(int, 10) |
Builtins
| Go | goish |
|---|---|
len(x) |
len!(x) |
append(s, x, y) |
append!(s, x, y) |
delete(m, k) |
delete!(m, k) (owned-key variables: delete!(m, &k)) |
for i, v := range s |
range!(s, |i, v| { ... }) |
Errors
let err = New;
let wrapped = Wrap;
if Is
errors::New returns error directly — no Some/Ok wrapping. err != nil
works because error implements PartialEq<error> with the nil constant.
fmt and Stringer
Println!;
let s = Sprintf!;
let e = Errorf!;
stringer!
Println!; // → color: #ff0000
Goroutines and channels
let jobs: = chan!;
let g = go!;
for _ in 0..3
let _ = g.Wait;
Goroutines run as tokio async tasks — ~200 B each. Proven at 1,000,000
concurrent goroutines (see tests/million_goroutines.rs), which is what
Go's scheduler handles natively. Inside go!{}, channel method calls look
identical to outside — a proc-macro (goish-macros) rewrites .Send,
.Recv, .Wait into their async equivalents at compile time.
net/http
use *;
Client:
let = Get;
if err != nil
defer!
let body = resp.Body.String;
Println!;
Context-bound deadlines cancel in-flight requests, same shape as Go:
let = WithTimeout;
let = NewRequestWithContext;
let = Do; // aborts after 5s if server is slow
Inside handlers, select! on r.Context().Done() to abort work when the
client disconnects. Backed by hyper 1.x + tokio; goroutine-per-connection.
defer!
// prints: work, inner, cleanup (LIFO, same as Go)
sync
let mu = new;
// auto-unlock at scope end
let wg = new;
for _ in 0..4
wg.Wait;
time
let start = Now;
Sleep;
let elapsed = Since;
Printf!; // e.g. "100.3ms"
testing (v0.4) — port Go tests line-by-line
use *;
// Go: type PathTest struct { path, result string }
Struct!
Go's TestMain ports as test_main! with a custom harness. In a
harness = false test target, use test_h! (not test!) and wire
setup/teardown around m.Run():
// tests/my_test.rs — registered with harness = false in Cargo.toml
use *;
test_h!
test_main!
Real Go tests ported as regression fixtures live in tests/:
tests/path_test.rs— direct port ofgo/src/path/path_test.gotests/itoa_test.rs— direct port ofgo/src/strconv/itoa_test.gotests/strings_compare_test.rs— port ofgo/src/strings/compare_test.gotests/bytes_test.rs— subset ofgo/src/bytes/bytes_test.go(Equal/Index/LastIndex/IndexByte)
Go's defer-recover ports as recover!{ … }:
// Go: defer func() { if r := recover(); r == nil { t.Fatal("want panic") } }()
// doPanickyThing()
let r = recover!;
if r.is_none
Packages (current status)
| goish | Go package | status |
|---|---|---|
fmt |
fmt |
Println/Printf/Sprintf/Fprintf/Errorf/Stringer |
errors |
errors |
New/Wrap/Is/Unwrap + Join/As |
bytes |
bytes |
Buffer |
strings |
strings |
20+ helpers + Builder |
strconv |
strconv |
Atoi/Itoa/ParseInt/ParseFloat/ParseBool/Format* |
time |
time |
Now/Since/Sleep + Duration arithmetic + Format/Parse/Date + Ticker/Timer/AfterFunc |
os |
os |
Args/Getenv/Exit + ReadFile/WriteFile/Mkdir + Hostname/Getwd + File/Open/Create + Stdin/Stdout/Stderr |
io |
io |
Reader/Writer traits, Copy, ReadAll, EOF |
bufio |
bufio |
Scanner + NewReader/NewWriter |
log |
log |
Println/Printf/Fatalf/Panic with timestamp |
sort |
sort |
Ints/Strings/Float64s/Slice/SliceStable |
math |
math |
constants + Abs/Pow/Sqrt/trig/log/IsNaN/IsInf |
filepath |
path/filepath |
Join/Base/Dir/Ext/Clean |
sync |
sync |
Mutex/RWMutex/WaitGroup/Once + atomic::{Int32,Int64,Bool} |
context |
context |
Background/WithCancel/WithTimeout + WithValue/WithDeadline |
chan |
channel builtins | Chan<T> + chan!(T[, n]) (see gaps) |
defer! / go! |
defer / go keywords | macro form |
unicode |
unicode |
IsLetter/IsDigit/IsSpace/IsUpper/IsLower/ToUpper/ToLower |
utf8 |
unicode/utf8 |
RuneCountInString/ValidString/DecodeRuneInString/EncodeRune/RuneLen |
rand |
math/rand |
Int/Intn/Int63/Float64/Seed/Shuffle (xoshiro256**, no deps) |
base64 |
encoding/base64 |
StdEncoding/URLEncoding/RawStdEncoding |
hex |
encoding/hex |
EncodeToString/DecodeString |
flag |
flag |
String/Int/Bool/Float64/Duration + Parse/Args/Arg |
const_block! |
const (…) with iota |
auto-incrementing constants |
bytes |
bytes (complete) |
Buffer/Reader/NewReader + Equal/Index/Split/Join/Trim/Replace/Fields/… |
strings |
strings (extended) |
+ Reader/Replacer/Map + IndexAny/ContainsAny/Title |
path |
path |
slash-only Base/Dir/Ext/Join/Clean/Split/Match (URLs) |
sort |
sort (extended) |
+ Search/SearchInts/SearchStrings/IntSlice/Reverse/ReverseInts |
runtime |
runtime |
NumCPU/GOMAXPROCS/Gosched/GOOS/GOARCH/NumGoroutine/Version |
exec |
os/exec |
Command/Run/Output/CombinedOutput/LookPath |
binary |
encoding/binary |
BigEndian/LittleEndian + Uvarint/Varint |
csv |
encoding/csv |
Reader/Writer with RFC 4180 quoting |
hash::{crc32,fnv} |
hash/crc32, hash/fnv |
IEEE CRC-32, FNV-1/1a 32/64 |
mime |
mime |
TypeByExtension/ExtensionsByType/AddExtensionType |
container::{list,heap} |
container/list, container/heap |
doubly linked list, generic binary heap |
url |
net/url |
Parse/URL/Userinfo + Values + QueryEscape/PathEscape |
crypto::{md5,sha1,sha256} |
crypto/md5, crypto/sha1, crypto/sha256 |
hand-rolled (no deps) |
json |
encoding/json |
Marshal/Unmarshal/MarshalIndent over Value |
regexp |
regexp |
Compile/MustCompile/MatchString/FindString/ReplaceAll/Split |
testing |
testing |
T/M/B + test!/test_h!/benchmark!/test_main! + Struct! |
net/http |
net/http |
Server + Client (hyper-backed): HandleFunc/ListenAndServe/ServeMux/Request/Response/ResponseWriter/Client/Get/Post/Do/NewRequestWithContext |
Known gaps
stringis an alias forString(owned, mutable). Go strings are immutable shared bytes; close enough for most code.
Examples
Design priority
The top priority is Go idioms and call-site syntax — if a Go programmer
reads a goish program, it should look like Go. Rust idioms (Option, Some,
&mut, trait-bound generics) live under the hood. When a Rust idiom has to
leak, it's called out explicitly and a wrapper is designed to minimise it.
For the goroutine + channel runtime decision (tokio + flume, 1M goroutines),
see docs/scheduler.md.
License
Dual-licensed under either of:
at your option.