Central question: is Rust a programming language you want to use for scientific computing and/or data analysis?
Features:
Challenges:
Overhyped? Judge for yourselves
Result, tests, seeds, and reproducible runscargo check as the edit-check loopCargo.toml and src/main.rsCargo.toml, Cargo.lock, and src/main.rscargo check, cargo build, and cargo runproject-name/
├── Cargo.toml
└── src/
└── main.rs
rustc is the compilercargo is the project workflowmain is the binary entry pointprintln! writes to standard output! marks a macro callcheck is fast feedbackbuild produces the executablerun builds and executescargo check againclap--: pass arguments to the program--help: generated by clapCargo.tomlCargo.locksource-code/hello-worldsource-code/hello-clap -- --helpCargo.tomlcargo check while editing?Cargo.lock for training examples?num-complex for complex arithmeticusize and isize match pointer widthi8, i16, i32, i64, i128, isizeu8, u16, u32, u64, u128, usizef32, f64bool, charf32 and f64 constants are distinct17 / 5 with 17.3 / 5.2% for integers and floating-point values/ discards the fractional part% gives the remainder for the same division rule/ and % use truncating divisiondiv_euclid and rem_euclid use Euclidean divisionf32 and f64 remain distinct typespowi takes an integer exponentpowf takes a floating-point exponentf64powi(2) matches an integer exponentf64as f64 marks the conversion pointas f64 conversionf32x, a, b, and c are typedComplex64 stores f64 real and imaginary partsz1 - z2z1.re and z1.imf64 arithmeticsource-code/basic-types/, %, div_euclid, and rem_euclidas f64 conversion and run cargo checkz1 - z2 to the complex-number examplematch makes discrete choices explicitif and elsewhile, for, and rangesif, while, and forif expressions as valuesmatchmodgcd(a, b) valuesmainif And elseboolwhilewhile repeats while the condition is truemut allows local parameter bindings to changea is the return value0na_max and b_max1..=a_max with 1..a_maxgcd(9, 6)->if Expressionsif has a valuematchresultsrc/
├── main.rs
├── simpson.rs
└── gauss.rs
main.rs handles setup and dispatchsource-code/control-flowa_max and b_max1..=a_max with 1..a_maxsource-code/numerical-functionenum-match methodssin to cosmatch diagnosticmain.rs?mut when a binding changes&[T] over &Vec<T> for read-only sequences&mut [T] for in-place sequence updatesmutmut belongs to the bindingmut and run cargo checkmutx changes on each iterationdelta_x does not changemutdxdt is borrowed mutably*dxdt writes through the reference&mut at the call site*dxdt assignmentrhs to return f64&mut dxdt grants temporary write accessf64 are copiedVec<f64> owns heap-allocated dataysxs is no longer usable after the moveclone creates an explicit copyxs after the call&xs grants read-only accessxs can still be used after the call&[f64] is a borrowed view of contiguous values&mut [f64] grants write access to sequence elementsiter_mut yields mutable element references*value writes through each reference&mut datafirst_value borrows from datanormalize(&mut data) needs exclusive accessfiltered stores references into xsxs remains borrowed while those references are usedcopied turns &f64 into f64xs can be modified independently afterward&[T]&mut [T]source-code/mutable-variablesmutrhs to return f64mean_borrow to use a slicecargo checkshift(data: &mut [f64], offset: f64)self or &mut selfmain.rs focused on program flowstruct with named fieldsimpl blockself, &self, and &mut selfnewdata.len() should match rows * colspub struct Matrix makes the type visiblenewnew belongs to MatrixselfSelf means Matrix inside this impl&self&self means shared access&mut self&mut self means mutable accessget only needs shared accesssetdata directlymain.rs Focusedsrc/
├── main.rs
└── matrix.rs
main.rs handles CLI and program flowmatrix.rs defines the domain typelen(&self) -> usizematrix.len() from main.rsget and set use the same mappingT is the element typeMatrix<f64> stores floating-point valuesMatrix<i32> stores integer valuesMatrix<f64>Matrix<i32>Matrix<bool> or Matrix<char>TMatrix<T>Some(index) means the coordinates are validNone means the coordinates are out of boundsT does not need to implement Copy or CloneResult makes failure explicitClone bound belongs on this implementationT: Clonesource-code/structs-and-methodslen(&self) -> usizematrix.dataindex helpersource-code/generic-structsMatrix<bool> or Matrix<char>Clone bound&self?&mut self?Index, IndexMut, Display, and TryFromOutput and Errordyn Trait as dynamic dispatch{}Index: read with matrix[(row, col)]IndexMut: assign with matrix[(row, col)] = valueDisplay: print with println!("{matrix}")TryFrom: build from nested vectors falliblyIntoIterator: loop over matrix valuesIndex(usize, usize)type Output = T defines the indexed value typeIndex implementationIndexMut&mut self grants mutable matrix accessset methodDisplayDisplay controls {} formattingT: Display is needed for each elementfmt::Result reports formatting success or failureT: DisplayTryFromTryFrom represents conversion that can failtype Error = String defines the error typeTryFromResult<Matrix<_>, String>IntoIterator&T&mut TDisplay separator&matrix&mut matrixdyn TraitBox<dyn QuadratureRule> stores either implementationintegrate works through the trait objectname works through the same interfacesource-code/traitsDisplay separatorTryFrom error path with ragged rows&matrix without consuming it&mut matrix and scale valuesname implementationpushiter, iter_mut, and into_iterfilter, map, zip, unzip, and enumeratesum, fold, and scanHashMap for countsHashSet for unique valuesx and y valuesVec<T> stores a growable sequenceserde maps records to a Rust structcsv handles parsing the file formatf64? propagates parse or I/O errorscopiediter() yields &f64copied() turns &f64 into f64collect consumes the pipelinemap transforms each itemcollect builds a concrete collectionzipzip combines two iteratorsunzipunzip splits an iterator over pairssum is concise for common accumulationfoldfold carries an accumulatorscanscan keeps state between itemsenumerateenumerate attaches an index(index, value)x.sqrt() for non-negative xyx + yHashMap<K, V> stores values by keyentry selects or creates a map entryor_insert(0) provides the initial count0write! and writeln! format textVec<T>HashMap<K, usize>HashSet<T>&[T]source-code/iteratorsmap pipelinefold and scan reductionszip to compute x + ycount-nucleotidesHashMapcollect need a type annotation?OptionmapResult?Some, None, Ok, and ErrOption and ResultOption to Result? to keep the success path readableexpect only when failure means a bugmain functionssetgetmatrix.rsOptionSome contains a valueNone means no value is presentSome(flat_index)NoneOption With mapmap transforms the value inside SomeNone stays Nonematch Equivalentmap versionResultOk contains the successful valueErr contains failure informationResultOk(())Err(String)Option To ResultSome(index) becomes Ok(index)None becomes Err(message)? OperatorOk(index) extracts indexErr(error) returns early from the function?? is shorthand for this common patternexpect documents that assumptionget still returns OptionNoneexpect is for impossible-in-this-context failureNone At The Call SiteErr At The Call SiteOption Or Result?Option<T> when absence is enough informationResult<T, E> when failure needs explanationexpect when failure means a bugmatch when the program can continue differently? when the current function should propagate failureResult for expected operational failureResultOption or Resultmainmain can return Result? can be used in command-line programs? propagates both failuresget with manual matchNoneErrset without ?source-code/error-handlingOption and Resultget with matchset without ?ok_or_else messageexpect document a real invariant?main simplify the program?src/lib.rs for shared package codecargo run --bin#[cfg(test)] and #[test]assert! and assert_eq!src/
└── main.rs
src/
├── lib.rs
├── generate-data.rs
├── read-errors.rs
└── count-nucleotides.rs
Cargo.tomlmaincargo run --bin NAME selects the executable--src/lib.rs defines reusable package codepub exposes items to binariesmain?#[cfg(test)] compiles the module for tests#[test] marks a test casesupersuper refers to the parent moduleassert! checks a Boolean conditionassert_eq! compares expected equalityto_stringis_known_token[[bin]] sectionscargo run --binsrc/lib.rsis_known_tokencargo testsrc/lib.rs?main coordinate or implement domain logic?ValueEnum for distribution choicesValueEnumclap parses the enumimpl RealDistribution {
fn from_kind(kind: DistributionKind) -> Self {
match kind {
DistributionKind::Uniform => {
Self::Uniform(Uniform::new(0.0, 1.0).expect("valid uniform distribution"))
}
DistributionKind::Normal => {
Self::Normal(Normal::new(0.0, 1.0).expect("valid normal distribution"))
}
}
}
}uniform maps to [0.0, 1.0)normal maps to mean 0.0, standard deviation 1.0DistributionKindRealDistribution variantcargo run -- --helprayon::prelude::*into_par_iter on a rangeRAYON_NUM_THREADSz <- z * z + c
zinto_par_iter becomes availablefn iterate_z_matrix(z: &Matrix<Complex64>, c: Complex64, max_iterations: usize) -> Matrix<usize> {
let mut result = Matrix::new(z.rows(), z.cols(), 0);
for i in 0..z.rows() {
for j in 0..z.cols() {
let z_value = *z.get(i, j).expect("loop indices should be in bounds");
let iterations = iterate_z_value(z_value, c, max_iterations);
result.set(i, j, iterations)
.expect("loop indices should be in bounds");
}
}
result
}collect builds the result vectorhyperfinediffiterate_z_matrix functionsRAYON_NUM_THREADS=1Complex64 for complex arithmeticjulia-set-baselinejulia-set-mdarrayjulia-set-mdarray-expr-evaljulia-set-toml-configjulia-set-rayonview-fractal.pyz <- z * z + c
c is fixed for one runz varies across the gridmax_iterations caps the work per pointnorm() supports the escape testcols controls the real-axis spacingrows controls the imaginary-axis spacingi and j become coordinatesfn iterate_z_matrix(z: &Matrix<Complex64>, c: Complex64, max_iterations: usize) -> Matrix<usize> {
let mut result = Matrix::new(z.rows(), z.cols(), 0);
for i in 0..z.rows() {
for j in 0..z.cols() {
let z_value = *z.get(i, j).expect("loop indices should be in bounds");
let iterations = iterate_z_value(z_value, c, max_iterations);
result.set(i, j, iterations)
.expect("loop indices should be in bounds");
}
}
result
}#[arg(short, long, default_value_t = 1000)]
max_iterations: usize,
#[arg(short = 'x', long, default_value_t = 800)]
width: usize,
#[arg(short = 'y', long, default_value_t = 600)]
height: usize,
#[arg(short = 'r', long, default_value_t = -0.5125)]
c_real: f64,
#[arg(short = 'i', long, default_value_t = 0.5213)]
c_imag: f64,cmdarraymdarray Indexingserde maps TOML into a struct? propagates errorsc_real and c_imaginitialize_ziterate_z_valuemdarray variantjulia-set.toml and rerunSome(filename) enables outputlet position_distribution =
Uniform::new(0.0, 1.0).expect("position distribution bounds should be valid");
let velocity_distribution =
Normal::new(0.0, 1.0).expect("velocity distribution parameters should be valid");
let mass_distribution =
Uniform::new(0.1, 1.0).expect("mass distribution bounds should be valid");fn acceleration_on(&self, index: usize) -> (f64, f64, f64) {
let mut acceleration = (0.0, 0.0, 0.0);
for i in 0..self.num_particles() {
if i != index {
let dx = self.xs[i] - self.xs[index];
let dy = self.ys[i] - self.ys[index];
let dz = self.zs[i] - self.zs[index];
// accumulate contribution
}
}
acceleration
}let accelerations = self.accelerations();
let half_dt_squared = 0.5 * dt * dt;
for i in 0..self.num_particles() {
let (ax, ay, az) = accelerations[i];
self.xs[i] += self.vxs[i] * dt + ax * half_dt_squared;
self.ys[i] += self.vys[i] * dt + ay * half_dt_squared;
self.zs[i] += self.vzs[i] * dt + az * half_dt_squared;
}Option<String> becomes Option<Writer>as_mut borrows the optional writer mutablySome writes one recordNone does nothing--delta-time--seed--softening--num-particles--delta-timeVector3 type improve the code?ndarray: N-dimensional arraysnum-complex: complex scalar typesndarray-linalg: linear algebra extension traitsnalgebra: matrices, vectors, geometryfaer: dense linear algebra in Rustndarray-linalg: ndarray plus LAPACK-style operationsargmin: numerical optimization framework
diffsol: ODE and DAE solvingdifferential-equations: ODE, DDE, and SDE initial-value problemsode_solvers: ODE solver building blockpolars: DataFrame and query engine
plotly: interactive plots from Rust
hdf5: HDF5 access from Rust
Hopefully, you have determined what is
for you