Migrating from Python to Gossamer¶
Python code ports cleanly once three habits change: types are checked
before execution, absence is represented with Option<T>, and failures
are returned as Result<T, E> instead of raised as exceptions.
Quick Map¶
| Python | Gossamer |
|---|---|
x = 5 |
let x = 5 |
reassigning x |
let mut x = 5 |
def f(x): return x + 1 |
fn f(x: i64) -> i64 { x + 1 } |
class User: ... for data |
struct User { name: String, age: i64 } |
User("Ada", 36) |
User { name: "Ada", age: 36 } |
None |
Option<T> with Some(v) or None |
try / except |
Result<T, E> with ? or match |
isinstance dispatch |
enum plus match, or traits |
| list | Vec<T> with [...]; use #[...] for a fixed array and &[T] for a borrowed slice |
| dict | Map<K, V> with {key: value} and {} literals |
| set | Set<T> with #{...} literals, or typed BTreeSet<T> with #{...} |
collections.deque as queue |
Queue<i64> from Queue::from([a, b]), push, and FIFO pop |
collections.deque as deque |
Deque<i64> with explicit front/back methods |
| stack list | Stack<i64> from Stack::from([a, b]), push, and LIFO pop |
heapq min-heap |
MinHeap::from([...]); use MaxHeap::from([...]) for max-heap order |
asyncio.create_task |
go fn() { ... }() |
if __name__ == "__main__" |
entry-file top-level statements |
Gossamer 0.47 Syntax At A Glance¶
Python uses indentation and permits trailing commas in multiline literals and
calls. Gossamer uses braces, permits semicolons only between statements on one
line, and treats layout inside
delimiters differently: one-line lists require commas; multiline lists use
newlines. Multiline commas are accepted while porting code, but gos fmt
removes them.
struct User {
name: String
active: bool
}
fn rename(
user: User
name: String
) -> User {
User {
name: name
active: user.active
}
}
enum Lookup {
Found {
index: i64
user: User
}
Missing(String)
}
let user = User { name: "Ada", active: true } // one line needs commas
Index sequences with [], access struct fields by name and tuple fields by
number, and use get when absence is expected:
let users = #[user, rename(user, "Grace")]
let first = users[0] // Vec/array index; traps if out of bounds
let initial = first.name[0] // UTF-8 byte as i64, not a Python character
let pair = (first.name, first.active)
let enabled = pair.1
let mut by_name: Map<String, User> = Map::new()
by_name.insert(first.name, first)
let cached = by_name.get("Ada") // Map lookup returns Option<V>
let found = Lookup::Found {
index: 0
user: cached.unwrap()
}
Data Types¶
Use structs for records. Named structs are always constructed with braces:
struct User {
name: String
age: i64
}
let user = User { name: "Ada", age: 36 }
let older = User { age: 37, ..user }
Use enums for a closed set of shapes:
enum Event {
Click(i64, i64)
Message(String)
Closed
}
match event {
Event::Click(x, y) => println!("click {x} {y}")
Event::Message(text) => println!("{text}")
Event::Closed => println!("closed")
}
Option Instead Of None¶
let name: Option<String> = user_name()
let display = name.unwrap_or("anonymous")
if let Some(n) = name {
println!("hello, {n}")
}
None is not a value of every type. It only appears inside
Option<T>.
Result Instead Of Exceptions¶
try:
text = Path(path).read_text()
cfg = parse_config(text)
except Exception as e:
log(e)
cfg = default_config()
use std::{errors, fs}
fn read_config(path: &String) -> Result<Config, errors::Error> {
let text = fs::read_to_string(path)?
parse_config(&text)
}
let cfg = match read_config(&path) {
Ok(v) => v
Err(e) => {
log(&e)
default_config()
}
}
Comprehensions And Pipelines¶
Python:
Gossamer:
use std::iter
let total = iter::range_inclusive(1, 10)
|> iter::filter(|n: i64| n % 2 == 0)
|> iter::sum_by(|n: i64| n * n)
For stateful code, ordinary loops are still idiomatic:
Strings And Bytes¶
String is UTF-8. Indexing works on bytes, not Python code points.
Use UTF-8 helpers when code-point semantics matter. Use [u8] for
binary data.
HTTP responses can serve binary bodies directly:
Concurrency¶
Python async code usually becomes goroutines plus channels when work
must run concurrently:
let (tx, rx) = channel()
for url in urls {
let tx = tx.clone()
go fn() {
tx.send(http::get(&url, #[]))
}()
}
while let Some(result) = rx.recv() {
handle(result)
}
Close the sender when no more values will arrive, or coordinate with
sync::WaitGroup.
Visibility¶
Gossamer has three visibilities, and they are declared per item, per method, and per struct field.
| Annotation | Reachable from |
|---|---|
| none | the declaring module and its descendants |
pub(package) |
every module of the declaring package |
pub |
anything that depends on the package |
A package is the unit of distribution: one project.toml, one project id.
A module is a directory under src/. A module nested inside another is a
module descendant, and visibility flows inward only: a descendant reaches
its ancestors' private items, never the reverse.
// src/money/mod.gos
pub struct Amount {
pub currency: String,
cents: i64, // private representation
}
impl Amount {
pub fn new(currency: String, cents: i64) -> Amount {
Amount { currency: currency, cents: cents }
}
pub fn cents(&self) -> i64 { self.cents }
fn normalize(&self) -> i64 { self.cents } // private helper
}
pub(package) fn round_trip(a: &Amount) -> i64 { a.normalize() }
A pub type may keep private methods and private fields, so a struct with any
private field can only be built by the module that declares it. Importing does
not widen anything: a use is a spelling convenience, and visibility is
decided by where the name is used.
Coming from Python, this is the largest change in kind.
_name and __name are conventions the interpreter mostly does not enforce;
Gossamer's visibility is checked at compile time and a violation is an error,
not a lint. Anything you want another module to reach needs pub or
pub(package) written on it.
Standard Library Map¶
| Python | Gossamer |
|---|---|
Path(path).read_text() |
fs::read_to_string(path) |
Path(path).read_bytes() |
fs::read(path) |
Path(path).write_text(s) |
fs::write(path, s) |
os.environ.get("X") |
env::var("X") |
sys.argv |
env::args() |
subprocess.run([...]) |
process::run(program, &args) |
print(x) |
println!("{x}") |
json.dumps(v) |
encoding::json::encode(v) |
json.loads(s) |
encoding::json::decode::<T>(s) |
re.compile(p) |
regex::compile(p) |
s.strip() |
strings::trim(&s) |
int(s) |
strconv::parse_i64(&s) |