Rush language reference
Fast as C++, easy as Python.
Rush is a small, statically-typed language for competitive programming. It
transpiles to C++20 and is compiled with g++ -O2, so a Rush solution runs at
the same speed as the equivalent hand-written C++ — but the syntax stays close
to pseudocode. This document is the complete language reference.
Run locally with the bundled compiler:
rush run solution.rush # compile and execute
rush build solution.rush -o sol # compile to a native binary
rush transpile solution.rush # print the generated C++ (to read/learn)
1. Program structure
Every program has an fn main(). Execution starts there.
fn main() {
print("Hello, Rush!");
}
Comments are C-style:
// line comment
/* block
comment */
Statements end with ;. Blocks use { }.
2. Types
| Rush | C++ | Notes |
|---|---|---|
ll |
long long |
64-bit signed — the default integer |
int |
int |
32-bit signed |
float |
double |
64-bit floating point |
bool |
bool |
true / false |
str |
std::string |
text |
void |
void |
function-return only |
vec<T> |
std::vector<T> |
dynamic array; nests, e.g. vec<vec<ll>> |
Mixing integer types widens to ll. Mixing an integer with a float produces a
float.
3. Literals
42 // ll
1000000007 // ll (64-bit, no overflow)
0xFF // ll — hex literal (bit patterns, masks)
0x8000000000000000 // hex may use all 64 bits (top bit set -> negative ll)
3.14 // float
1e9 // float (exponent form)
1.5e-3 // float
true false // bool
"hi\n" // str — escapes: \n \t \\ \" \0
[1, 2, 3] // vec<ll> literal; [[1,2],[3,4]] nests to vec<vec<ll>>
A vector literal's element type is unified from its elements (numbers widen);
assigning it to a declared vec<T> adopts T. An empty [] is rejected —
declare the vector and push instead.
4. Variables
ll x = 5; // declare + initialise
ll a, b, c; // several at once (value-initialised to 0)
ll a = 1, b = 2; // several with initialisers
float pi = 3.14159;
str name = "rush";
bool ok = true;
vec<ll> v(n); // a vector of n zeros (constructor-style size)
vec<ll> w; // an empty vector
vec<vec<ll>> grid(n); // n empty rows
An uninitialised scalar starts at 0 / false / "" (predictable, unlike C++).
5. Operators
Arithmetic: + - * / %
Division follows the operand types, exactly like C++:
print(7 / 2); // 3 — integer division (both operands integer)
print(7.0 / 2); // 3.5 — real division (one operand is float)
% (modulo) is integer-only.
Bitwise & shift (integer-only): & | ^ ~ << >> >>>
ll m = (1 << n); // 2^n
if ((mask & (1 << i)) != 0) { ... } // is bit i set?
ll low = x & (-x); // lowest set bit
ll hi = x >>> 1; // UNSIGNED right shift: 0s fill from the top
>> is the arithmetic shift (keeps the sign); >>> is the logical shift
(fills with zeros) — the one you want when a 64-bit value is a bit pattern
(bitboards, masks, hashes) rather than a number. See also popcount, ctz,
clz, and umul in the standard library.
Comparison: == != < <= > >= (numbers, and str for == != < ...)
Logical: && || ! (operands must be bool)
Membership — in (yields bool):
if ("cad" in text) { ... } // substring test on strings (KMP, fast)
if (x in seen) { ... } // set<T> / uset<T> membership
if (key in m) { ... } // map<K,V> / umap<K,V> — is it a key?
if (v in nums) { ... } // vec<T> / arr<T> — linear scan
(for (x in v) is the separate range-for form, not this operator.)
Conditional (ternary): cond ? a : b
ll sign = x > 0 ? 1 : (x < 0 ? -1 : 0);
print(ok ? "YES" : "NO");
Assignment: = and compound += -= *= /= %= &= |= ^= <<= >>=
(+= also concatenates strings). Increment/decrement: ++ --.
Precedence matches C++. Rush fully parenthesises the generated C++, so what you
write is what you get.
6. Control flow
if (x > 0) {
print("pos");
} else if (x < 0) {
print("neg");
} else {
print("zero");
}
while (n > 0) { n--; }
for (ll i = 0; i < n; i++) { print(i); }
for (x in v) { sum += x; } // range-for over a vec/arr/deque/set/...
A condition may be a bool or a number (non-zero is true), so while (m--) and
if (flag) work as expected. break and continue behave as in C.
7. Functions
fn add(ll a, ll b) -> ll {
return a + b;
}
fn greet(str who) { // no '-> Type' means void
print("hi", who);
}
Functions may call each other in any order and may recurse. A ref parameter
binds by reference, so writes are visible to the caller:
fn addone(ref ll x) { x += 1; }
fn main() {
ll v = 41;
addone(v);
print(v); // 42
}
8. Input & output
ll n;
input(n); // read one whitespace-separated value
ll a, b;
input(a, b); // read several at once
str line = read_line(); // read a WHOLE line (spaces included)
print(a, b); // values space-separated, then a newline
print("yes\n"); // a value ending in \n is not double-terminated
print(); // just a newline
flush(); // force stdout out, useful for interactive protocols
input(...)reads whitespace-separated tokens (ints, floats, words).flush()flushes stdout so another process can read responses immediately.read_line()reads the rest of the current line. If you mixinput()and
read_line(), rememberinput()leaves the newline in the buffer — a
followingread_line()may return the empty tail of that line.eof()istrueonce a read has hit the end of standard input — use it to
terminate awhile (true) { str line = read_line(); ... }protocol loop.stdin_ready()istruewhen input (or EOF) is waiting on stdin right
now, without blocking — poll it inside a long computation so a protocol
command like UCI'sstopcan interrupt the work.floatvalues print with 12 significant digits, enough to clear the usual
1e-6checker tolerance.
Files and time:
str text = read_file("weights.txt"); // whole file as text ("" if unreadable)
if (file_exists("weights.txt")) { ... }
ll t0 = clock_ms(); // monotonic wall clock, milliseconds
// ... work ...
print(clock_ms() - t0, "ms");
9. Standard library
Everything below is built in — no imports. Types are written with their type
arguments, e.g. Fenwick<ll> bit(n).
Free functions
| Call | Meaning |
|---|---|
min(a, b), max(a, b) |
smaller / larger (two numbers or two strings) |
abs(x) |
absolute value |
gcd(a, b), lcm(a, b) |
integer gcd / lcm |
swap(a, b) |
swap two variables/elements |
sort(v) |
sort a vec ascending |
reverse(v) |
reverse a vec |
unique(v) |
dedupe a sorted vec; returns new length |
sum(v) |
total of a numeric vec (ll, or float) |
len(v) / size(v) |
length of a vec (ll) |
push(v, x) / pop(v) |
append / remove-last on a vec |
compress(v) |
coordinate-compress a vec; returns #distinct |
to_str(x) |
number/bool/str → str |
to_int(s) |
str → ll |
to_float(s) |
str → float |
popcount(x) |
number of set bits in the 64-bit pattern |
ctz(x) |
count trailing zeros — index of the lowest set bit (64 if x == 0) |
clz(x) |
count leading zeros (64 if x == 0) |
umul(a, b) |
wrapping unsigned 64-bit multiply (mod 2^64; overflow-safe for hashes) |
ord(s) |
character code of s's first byte (-1 if empty) |
chr(n) |
one-character str from a character code |
read_file(path) |
whole file as str ("" if unreadable) |
file_exists(path) |
bool — can the file be opened? |
clock_ms() |
monotonic wall clock in milliseconds (ll) |
eof() |
bool — has stdin hit end-of-file? |
stdin_ready() |
bool — is input (or EOF) waiting on stdin? (non-blocking) |
v16_add(a, b, off, n) |
SIMD: a[j] += b[off+j] for j < n (both i16arr) |
v16_sub(a, b, off, n) |
SIMD: a[j] -= b[off+j] |
v16_copy(a, b, off, n) |
SIMD: a[j] = b[off+j] |
v16_dotc(a, w, n) |
SIMD: Σ clamp(a[j],0,127)·w[j] → ll (NNUE dot product) |
The v16_* kernels run element-wise math over i16arr at 16–32 SIMD lanes
(the backend auto-vectorizes them; build with rush build --native for the
full width of the local CPU). i16arr values wrap at ±32768 — it is a
storage type for small numbers, built for accumulator math.
| prefix_function(s) | KMP prefix function → arr<ll> |
| z_function(s) | Z-function → arr<ll> |
| sqrt, floor, ceil, pow, hypot, sin, … | floating-point math (<cmath>) |
str methods
s.size(), s.substr(pos, len), s.find(t) (index or -1),
s.find_all(t) (arr<ll> of indices), s.contains(t) (bool),
s.split() (whitespace-tokenize → vec<str>). Also t in s tests substring.
s[i] yields the one-character str at position i (read-only — build a new
string to modify). Pair with ord/chr for character arithmetic:
str sq = "e4";
ll file = ord(sq[0]) - ord("a"); // 4
ll rank = ord(sq[1]) - ord("1"); // 3
Containers
| Type | Highlights |
|---|---|
vec<T> |
dynamic array, indexable, iterable, sort/reverse/push/pop |
arr<T> |
like vec but with method syntax: a.push(x), a.sort() |
stack<T> |
push/pop/top/size/empty |
queue<T> |
push/pop/front/back/size/empty |
deque<T> |
push_back/push_front/pop_back/pop_front/front/back, indexable |
pq<T> |
max-heap: push/pop/top |
minpq<T> |
min-heap: push/pop/top |
i16arr |
array of 16-bit ints: a(n), a[i], a.size() — pair with the v16_* SIMD kernels |
set<T>/uset<T> |
ordered / hashed set: insert/erase/contains, iterable |
map<T,U>/umap<T,U> |
ordered / hashed map: m[k], contains/erase |
pair<T,U> |
fields .first, .second |
Structures & algorithms
| Type | Constructor | Operations |
|---|---|---|
Fenwick<T> |
Fenwick<ll> f(n) |
f.add(i, v), f.sum(l, r), f.prefix(i) |
SegTree<T> |
SegTree<ll> s(n) |
s[i]=v / s[i]+=v / s.set/add(i,v); from the same tree: s.sum(l,r), s.min(l,r), s.max(l,r), s[i]; walking: s.first_ge/first_le(l,x), s.last_ge/last_le(r,x), s.prefix_ge(x) → index or -1 |
LazySegTree<T> |
LazySegTree<ll> z(n) |
z.add(l,r,v), z.set(l,r,v), z.sum(l,r) |
DSU |
DSU d(n) |
d.union(a,b), d.same(a,b), d.find(x), d.size(x), d.count() |
Graph |
Graph g(n) |
g.add_edge/add_dir(u,v), g.deg(u), g.size() |
WGraph |
WGraph g(n) |
g.add_edge/add_dir(u,v,w), g.dijkstra(src) -> arr<ll> |
ModInt<MOD> (alias mint) |
mint<998244353> a = 5 |
+ - * /, a.pow(e), a.inv(); prints as its value |
All ranges are inclusive [l, r] and all indices are 0-based.
10. A complete example
Range-sum queries with a Fenwick tree:
fn main() {
ll n, q;
input(n, q);
Fenwick<ll> bit(n);
for (ll i = 0; i < n; i++) {
ll x;
input(x);
bit.add(i, x);
}
for (ll k = 0; k < q; k++) {
ll l, r;
input(l, r);
print(bit.sum(l, r)); // inclusive
}
}
Geometry with float:
fn main() {
float x1, y1, x2, y2;
input(x1, y1, x2, y2);
float dx = x2 - x1;
float dy = y2 - y1;
print(sqrt(dx * dx + dy * dy)); // any <cmath> function is available
}