Skip to content
mutastic

Rust edition

Operators

An operator is a kind of change; each mutant is one operator applied at one place. mutastic for Rust uses the Java edition’s ids wherever the meaning is the same, so a rule, a script or an agent that names mutastic/boundary means one thing in both editions, and adds a few ids of Rust’s own.

A run uses defaults unless --operators says otherwise: --operators=stronger adds the stronger set to the defaults, --operators=extended adds both, and a list of ids separated by commas picks exactly those. Code inside unsafe, and safe functions holding an unsafe block, get no mutants unless --unsafe=enclosing or --unsafe=all asks (see Unsafe code).

Each operator has its place on a family’s page: what it changes, the bug it stands for, an example from fixtures/operators with the card mutastic prints for it, and the test that catches it. The families are the Java edition’s, with the Java edition’s names wherever the language allows:

  • Conditions: comparisons, conditions, and how two combine.
  • Arithmetic: operators, numbers and bits.
  • Returns and errors: what a function returns, and the error it gives.
  • Calls and writes: calls, assignments and writes left out.
  • Arguments: what a call is given, and what a function gets.
  • Library methods: std’s methods swapped for their counterparts, and the text in your code.
  • Iterators: iterator stages and consumers, where the Java edition has streams.
  • Option and Result: how they’re asked about and unwrapped, where the Java edition has Optional.
  • Control flow: match arms and loops.
OperatorWhat it changesSetFamilyIn Java
mutastic/boundary< and <=, > and >=; lo..hi and lo..=hidefaultsConditionsthe same
mutastic/remove-conditionala condition to always true, and to always falsedefaultsConditionsthe same
mutastic/logical-connector&& and ||defaultsConditionsthe same
mutastic/comparisona comparison to the one that differs on one side: < to !=strongerConditionsthe same
mutastic/other-comparisona comparison to the others; a condition to its oppositestrongerConditionsthe same
mutastic/math+ and -, * and /, and their checked_, wrapping_, saturating_ and overflowing_ methodsdefaultsArithmeticthe same
mutastic/arithmetic-replacement+ to *strongerArithmeticthe same
mutastic/other-arithmetican arithmetic operator to each of the othersstrongerArithmeticthe same
mutastic/keep-operanda + b to a, and to bstrongerArithmeticthe same
mutastic/remove-negation-x to xstrongerArithmeticthe same
mutastic/constant-valuea number to the ones next to it, 0, 1 and -1strongerArithmeticthe same
mutastic/bitwise&, | and ^; << and >>strongerArithmeticthe same
library/unchecked-arithmeticchecked_add to a wrapping_add that can’t failstrongerArithmeticthe same
mutastic/return-valuea returned value to another of its type: 0, the other bool, None, an empty onedefaultsReturns and errorsthe same
mutastic/unwrap-tryf()? to f().unwrap()defaultsReturns and errors—
mutastic/replace-bodya function’s whole body to one value, or to nothingextendedReturns and errorsthe same
mutastic/remove-throwreturn Err(e); or panic!(..); removed, so the code carries onextendedReturns and errorsthe same
mutastic/remove-void-calla call that returns nothing, removeddefaultsCalls and writesthe same
mutastic/remove-discarded-calla call whose result is thrown away, removeddefaultsCalls and writesthe same
mutastic/remove-assignmentan assignment to a variable that already has a value, removeddefaultsCalls and writesthe same
mutastic/remove-field-writea write to a field, through a reference, or to an element, removedstrongerCalls and writesthe same
mutastic/remove-struct-fielda field set in a struct literal with ..base, left to basestrongerCalls and writes—
mutastic/omit-calla call that returns what it’s given, left out: s.trim() to sstrongerArgumentsthe same
mutastic/swap-argumentsa call’s last two arguments of one type, swappedstrongerArgumentsthe same
mutastic/null-parameteran Option parameter, None from the start of the functionstrongerArgumentsthe same
mutastic/drop-varargthe last value of an array or vec! passed to a call, left outextendedArgumentsthe same, in stronger
mutastic/default-parametera parameter, its type’s default from the start of the functionextendedArguments—
library/swap-stringstarts_with and ends_with, to_uppercase and to_lowercase, trim_start and trim_end, strip_prefix and strip_suffixdefaultsLibrary methodsthe same
library/swap-mathmin and max, floor and ceildefaultsLibrary methodsthe same
library/swap-index-offind and rfind, position and rposition, split_once and rsplit_oncestrongerLibrary methodsthe same
library/reverse-orderinga.cmp(&b) to b.cmp(&a); .reverse() left outstrongerLibrary methods—
mutastic/string-constanta string to "", and "" to "mutastic"strongerLibrary methodsthe same
mutastic/regex-constanta regular expression to one that differs and still compilesextendedLibrary methodsthe same
bigdecimal/remove-stepround_dp(2) and the like, left outextendedLibrary methodsthe same, in a plugin
stream/swap-matchany and alldefaultsIteratorsthe same
stream/remove-stage.filter(p), .skip(n), .take(n), .skip_while(p), .take_while(p), .step_by(n) or .rev(), left outdefaultsIteratorsthe same
stream/remove-other-stage.inspect(f) or .map(f), left outextendedIteratorsthe same
optional/swap-presentis_some and is_none, is_ok and is_errstrongerOption and Resultthe same, in extended
optional/emptySome(x) to NonestrongerOption and Resultthe same, in extended
optional/always-defaulto.unwrap_or(d) to dstrongerOption and Resultthe same, in extended
optional/no-defaulto.unwrap_or(d) to o.unwrap()strongerOption and Resultthe same, in extended
optional/remove-filtero.filter(p) to oextendedOption and Resultthe same
mutastic/remove-switch-arma match arm stops matching, so its values go to a later armstrongerControl flowthe same, in extended
mutastic/swap-breakbreak and continueextendedControl flow—

mutastic/return-value(extended) in a set turns on return value’s extended forms, a return of Default::default() for a type that visibly has one, and an Err(..) the function returns elsewhere; it isn’t an operator of its own, and its mutants are mutastic/return-value’s.

Every mutant here compiles whenever the code it changes did, or is withdrawn by the build: a site’s mutants and its original code are arms of one match, so a change that gives the expression another type is rejected by the compiler and reported as unviable, never run.

The Java edition has 44 operator ids. 38 carry over with the same meaning, all implemented; 6 don’t, for want of the language feature they change. mutastic for Rust adds 5 ids of its own, each in the family it fits, and implements one of the Java edition’s plugin operators.

  • Not carried over (6): mutastic/flip-increment (no ++; += and -= are math’s), library/omit-argument (no overloading), the three predicate/* ids (no Predicate combinators in std; && and ! inside a closure are logical connector’s and other comparison’s), mutastic/drop-catch-type (no multi-catch).
  • Rust only (5): mutastic/unwrap-try, mutastic/remove-struct-field, mutastic/default-parameter, library/reverse-ordering, mutastic/swap-break.
  • From the Java edition’s plugins: bigdecimal/remove-step, for rust_decimal. The others don’t carry: bigdecimal/swap-arithmetic and swap-scaling come free through math and swap-math, as Decimal uses operators; kotlin/empty-return’s empty values are Rust’s own, and return-value’s; annotations/* can’t sit behind a run-time switch, as attributes act at compile time.

Some ids sit in another set than in Java: the four optional/* ids and remove-switch-arm are in stronger, because Option, Result and match are core Rust, not a modern API; drop-vararg is in extended, as an array’s length is part of its type, and more of its mutants are withdrawn. mutastic/unwrap-try, Rust’s own, is in the defaults: a ? whose error no test makes happen is an error path never run, and on real crates most of its missed mutants are just that.

Not built yet, each for its reason:

  • concurrency/weaken-ordering (SeqCst to Acquire/Release to Relaxed): it almost never changes what a test sees on x86-64 or Apple Silicon, and load(Release) panics, so it needs loom or Miri to mean anything; it would never be scored.
  • Removing x.clone(): it mostly fails the borrow checker, and where it compiles it’s usually equivalent; evidence about performance, not wrong output.
  • Ghosts, the missing peer call inserted after a write that lacks it: not built in the Java edition either, so the two can share its design.
  • proposed/rewrite, a function’s body to a proposed one: waits on the proposals format and a measured run on real model output, as in the Java edition.
  • mutastic/narrow-cast (x as u32 to x as u16 as u32): no tool or bug count behind it yet.
  • Unsafe-specific operators (ptr.add(i), set_len(n), Layout sizes off by one): their mutants are undefined behaviour by design, so they mean something only with a Miri pass, which needs nightly.