Repository navigation
Changelog
26.6.0 (2026-11-10)
Features
-
Consecutive contract fields narrower than a slot now share storage slots, as Solidity packs state variables:
mut owner: Address, mut paused: bool, mut decimals: u8take one slot instead of three. A packed field is read and written in place, keeping the rest of its slot. Fields placed with#[slot(e)], structs and other aggregates still take whole slots. This moves existing contract fields to new slots and offsets, so data stored by a contract compiled with an earlier version must be migrated. (#1826) -
Solidity value types now pack into shared storage slots inside structs, tuples and map entries, like
booland the narrow integers already do:Addresstakes 20 bytes,FixedBytes<N>takesN, the odd-width integers such asUint24take their width, and any other newtype (a struct with a single field) packs like its field.struct Rec { owner: Address, flag: bool, stamp: u64 }now takes one slot, as in Solidity. Writing a value its bytes cannot hold, such as aFixedBytes<4>whosevalhas more than four bytes, reverts. This moves fields of existing structs, tuples and map values to new offsets, so data stored by a contract compiled with an earlier version must be migrated. (#1826) -
Owned values are destroyed exactly once when their owner gives them up: at the end of its scope, when overwritten, or for the fields a partial move leaves behind. A type releases what it holds in a
core::ops::Dropimplementation;core::mem::dropdestroys a value early andcore::mem::forgetgives it up without destroying it.MemVec,MemArray,MemBufferandFixedMemBufferfree their allocation and destroy their elements, and the newcore::ptr::Box<T>holds one value in an allocation of its own. Operations that allocate gain fallible forms, such astry_with_capacity,try_reserve,MemVec::try_pushandcore::ptr::try_alloc_bytes, andcore::ptr::deallocfrees raw allocations on native targets. A type may hold itself through a generic type that keeps its argument behind a pointer, as instruct Tree { children: MemVec<Tree> }. Seedocs/language/destruction.md. (#1870) -
&=and^=now work onbool. (#1882) -
With the
wasmCargo feature,fe build --backend wasmcompiles a file to a WebAssembly module (one linear memory, 32-bitusize) and a manifest of its imports and exports. Every non-genericpub fnat the root of the file is an export; besidesbooland integers of at most 64 bits, an export can takestd::wasm::HostTextand returnstd::wasm::Reply, andexternfunctions become imports.fe test --backend wasmruns each test as its own module under Node.js.std::wasmprovides the root effectstd::wasm::Host, whose capabilitiesLog,Clock,Abortand the read-only files contractFilesexports and tests take inuses, and pure Fe mocks of each.target = "wasm"infe.tomlselects this backend forfe checkandfe test. See "WebAssembly output" inCLI.md. (#1904) -
Added
core::hash, a streaming Keccak-256 that hashes borrowed bytes without allocating, on any target. (#1909) -
core::hint::black_boxnow works on native targets. Each use costs a call and a memory round trip. (#1911) -
Native
externdeclarations can name any C function, including those core calls (malloc,free,write,fflushandabort), as long as every declaration of a symbol has the same C signature. Conflicting declarations are an error that names both. An#[unsafe(export)]function can replace a function that native code imports, such asmalloc. (#1913) -
DynArray::from_mem_vecbuilds an array from aMemVecit consumes, destroying each element once it is encoded.Bytes::zeroedallocates a value to be filled throughspan, likeDynString::zeroed. (#1914) -
core::hashadds SHA-256:Sha256,sha256andsha256_wordwork like their Keccak-256 counterparts and run in software on every target. On the EVM,std::evm::crypto::sha256still uses the cheaper precompile. (#1927) -
core::textparsers andstd::evm::parse_addressaccept anycore::text::Text, implemented forString<N>,DynString,MemSpan,MemBufferandstd::env::Arg, so a process argument or a JSON string can be parsed without copying it:parse_uint(args.get(1)).MemSpangainsstarts_with,ends_with,index_of,trim,splitandlines:for (i, field) in row.split(", ").enumerate() { .. }. The searches andsplittake acore::text::Pattern, which is a byte (b',') or anyTextexcept thatsplitcan't yet take aMemSpan. (#1928) -
loop { .. }repeats its body until abreak. Alooporwhile truethat nobreakleaves never completes, so a function can end in one that returns from inside it, with nocore::panic()after it. (#1937) -
is_some,is_none,is_ok,is_errandunwrapwork on the optional accesses projections return, such asv.get(i)andv.last():v.get(0).unwrap()reads the element,v.last().unwrap() = xwrites it, andnode.child(0).unwrap().child(1)chains. On an empty variantunwrappanics as it does on a value. (#1938) -
core::map::HashMap<K, V>is a hash table that owns its keys and values, in the prelude.getgives the value in place (mut?),insertandremovereturn the old value,get_or_insertgives the entry's value for update (counts.get_or_insert(word, 0) += 1), andmap[key]panics on a missing key. Lookups take any key type the map's key compares with, so aDynStringkey is found with a literal or a span.for v in mapvisits the values, andkeys()andentries()drive loops over keys and(key, value)pairs. Keys implement the newcore::hash::Hashtrait, which feeds aHasher;FoldHasheris the map's. (#1939) -
Byte spans,
MemBufferandDynStringare ordered as text, and everyMemSliceofOrdelements is ordered element by element.MemSlice,MemArrayandMemVechave asortmethod that sorts elements of anyOrdtype in place by swapping them, so aMemVec<DynString>orMemVec<MemBuffer>can be sorted;core::sort::sortstill requiresCopyelements. (#1940) -
core::text::decode_hexdecodes hex digits of either case, with or without a0xprefix, toBytes:decode_hex("0x00ff"). It returnsNonefor an odd number of digits or a non-digit byte. (#1940) -
An out-of-range array index now reverts with
Panic(0x32), as in Solidity, instead of reverting without data. (#1941) -
UFixed::parseandFixed::parseread decimal text such as0.25or-1.5, with at most the type's number of fraction digits:Wad::parse("0.3").UFixedandFixedimplementstd::io::Display, writing the shortest exact text without trailing zeros, soformat!(Wad::from_ratio(5, 2))is2.5. (#1942) -
core::num::leading_zerosandtrailing_zerosaccept any integer type and return the count in that type. On native targets au64count is a single instruction. (#1966) -
MemSlice::as_chunks<N>()views a slice asN-element arrays and the elements after them, as Rust'sas_chunksdoes. An index into the chunks is checked against their count alone, so a loop over them needs no other check, and constant indices into a chunk need none.word_at(k)on byte chunks reads chunkkas the high bytes of a word. (#1971) -
DynString::spanandBytes::spanaremut?: amutspan writes the payload in place, leaving its length and padding unchanged, so a string allocated withzeroedcan be filled without raw pointers. (#1971) -
A byte span (
MemSpan), aMemBufferor a command-line argument (std::env::Arg) compares with a string, such as a literal:args.get(1) == "serve"checks a command-line argument. Slices (MemSlice<T>) compare with each other element by element. -
A native
pub fn main()may return nothing, and exits with 0. -
A trait effect now provides its supertraits, and theirs in turn: in a function that
uses (evm: mut Super), a call that needsCtxorCallfinds them. A supertrait does not provide its subtraits. -
Byte literals, such as
b'0'andb'\n', areu8values. -
Items and modules can be compiled conditionally with
#[cfg(..)], which keeps an item only when its predicate holds:target = "evm"or"native",target_os = "linux"or"macos", ortarget_family = "unix". An item with severalcfgattributes needs all of them to hold, and#![cfg(..)]at the top of a module applies to all of its items and to the modules in its directory. Native builds take the operating system from the host; EVM code and SP1 guests have none.cfgon fields, variants, associated items, extern functions andforloops is an error. -
Native panics now report on stderr and abort, after flushing output written before them:
panicked: arithmetic overflowfor a Solidity panic code, the message of anassert!that has one, other revert data in hex, andpanickedforcore::panic. They used to stop at a bareSIGILL, losing buffered output. Nativefe testnow supports#[test(should_revert)], checkingpanic = ...andselector = ...against the revert data as on EVM. SP1 guests still trap. -
Native programs receive standard output, error and input as effects. On a native target, a test or a top-level
pub fn maintakes its effects from the new root effectstd::native::Host, as contracts take theirs fromEvm:pub fn main() -> i32 uses (out: mut Stdout).std::ioprovides the sealed capabilitiesStdout,StderrandStdin, andStdio, which provides all three; the builtin macrosprint!,println!,eprint!andeprintln!, which write the text of eachDisplayargument (integers, strings, bytes,bool) in one unbuffered write, andformat!, which returns it in aMemBuffer;read_to_end; andStdinReader, which reads standard input a byte or a line at a time.std::envprovidesArgs(count(),get(i)) andEnv(lookup(name)), andstd::processprovidesExit(exit(status)). A test ormainthat asks for an effect its target's root effect does not provide, such asEvmon a native target orStdouton the EVM, is now a type error. -
The builtin macros
write!(mut buf, a, b, ..)andwriteln!append the text of eachDisplayargument to an existingMemBuffer,writeln!then a newline, asformat!builds a new one. The buffer is written as amutargument and may be a local, a field or an effect, as inuses (text: mut MemBuffer). -
The language server checks a project for the
targetin itsfe.toml, and rechecks when that changes. -
#[unsafe(export)]on a freepub fnmakes a native build define the function under its own name with the C calling convention, kept even when nothing in Fe calls it, so C or Rust code linked with the build can call it. An exported function cannot be generic or takeuseseffects, and its parameters and result must bei32,u32,i64,u64,usize,isizeor raw pointers. EVM and SP1 builds reject#[unsafe(export)]. -
MemBuffer::try_new(len)returns a buffer oflenzero bytes, orNonewhen the allocation fails. -
MemVec::swap_remove(index)removes and returns an element in constant time, moving the last element into its place. -
Option<T>compares with==and!=whenTdoes: two options are equal when both areNoneor both hold equal values. -
core::num::to_beandfrom_beaccept any integer type, the newto_leandfrom_leconvert to and from little-endian order, and the newswap_bytesreverses the bytes of any integer.MemSpanandMemBuffergainread_be,read_le,write_beandwrite_le, which read or write an integer of any width at any byte offset, reverting unless all its bytes are in range. -
fe build --backend native --emit objectwrites<name>.oand--emit staticlibwriteslib<name>.a, a library of the#[unsafe(export)]functions that C and Rust programs link like any static library. A library needs nomain. See "Native output" inCLI.md. -
fe build --backend nativelinks host code into an executable:--link <PATH>adds an object file or archive, and--link-lib <NAME>a library as-l<NAME>. A Feexternfunction can now be implemented in C or Rust, which can call the executable's#[unsafe(export)]functions back. -
fe build --emit sizeswrites<contract>.sizes.txt, the code bytes of each function in a contract's runtime and init sections, largest first. -
std::fsgives native programs on Linux and macOS the host's files and directories through the sealed capabilityFs, whichmainand tests take fromstd::native::Host:uses (fs: Fs)to read anduses (fs: mut Fs)to also change files.Fsprovidesread,write,append,create_dir_all,metadata(file type and size),read_dir(entry names and types) andcanonicalize. A path is anyDisplayvalue. A failure returns astd::io::Errorholding the host'serrno, whichkind()classifies (NotFound,PermissionDenied,AlreadyExists,NotADirectory,IsADirectory,InvalidInput,Other) and which displays as the host's message. -
std::processruns other programs on Linux and macOS through the sealed capabilitySpawn, whichmainand tests take fromstd::native::Hostwithuses (proc: mut Spawn). ACommandnames the program and sets its arguments, environment (env,env_remove), directory, standard streams and timeout:proc.output(Command::new("git").arg("status").stdout(Redirect::Capture)). Standard input is inherited,NullorBytes; standard output and error are inherited,Null, captured, written to aFile, or, for standard error, sent to standard output.spawnstarts aChildandwaitwaits for it, whilewait_anyserves several children at once and returns the index of one that has ended. AnOutputholds theStatus(an exit status, a signal, orTimedOut), the captured bytes, and theUsage: user and system CPU time and peak resident memory. A failure returns astd::io::Error. -
std::timegives native programs on Linux and macOS the host's clocks through the sealed capabilityClock, whichmainand tests take fromstd::native::Hostwithuses (clock: Clock).instant()reads the monotonic clock as anInstant, andnow()the wall clock as theDurationsince the Unix epoch. ADurationcounts nanoseconds in au64, withfrom_secs,from_millis,from_microsandfrom_nanos, the matchingas_*methods,+,-and comparisons;Instant::duration_sincegives the time between two readings, andinstant + durationa later one. -
targetinfe.tomlsets a project's target,"evm"or"native", on an ingot or a workspace.fe check,fe buildandfe testuse it unless--backendis passed.
Bugfixes
- Selector checks and JSON ABI emission honor scalar and array
SolCompat::SOL_TYPEdeclarations on structs with custom ABI codecs. (#1827) - A
Snapshotreceiver that is also provided as an effect, as inwith (Handle = handle) { handle.read() }wherereadusesmut Handle, is now copied before the call instead of being rejected with an access conflict (16-0001). Writes through the effect change the provided place, not the receiver the method reads. (#1828) - Calling through a mutable storage
Address, such astarget.call(..)on amut target: Addresscontract field in arecvarm, or through a function that uses it as an effect, no longer fails with an access conflict (16-0001).AddressisSnapshot, so it is copied before the call. (#1828) - At
-O 1and-O 2, a checked operation whose result is unused, such aslet m = b - (a & 255), now checks the operand as written. The optimizer dropped the& 255and checkedb - a, which reverted withPanic(0x11)fora = 2000andb = 1000. An^operand was dropped the same way, so2 - (a ^ 5)witha = 1checked2 - 1and did not revert. (#1853) - On native targets,
read_wordandwrite_wordofMemSpanandMemBuffer, andcore::abi's word reads and writes, use big-endian byte order as on EVM; they used the target's native order.core::num::to_beandfrom_beconvert a word between big-endian and the target's order. (#1870) Option::is_some,Option::is_none,Result::is_okandResult::is_errread the value instead of consuming it, so querying anOptionorResultthat owns a value leaves it usable. (#1870)- Division and remainder by zero in unchecked code now panic with
Panic(0x12), as in checked code and Solidity, instead of returning zero. (#1882) - Unchecked arithmetic now applies to
Wei,DurationandTimestamp, and to any type that implements the wrapping operator traits (WrappingAdd,WrappingSub,WrappingMul, and the newWrappingPowandWrappingNeg); operators on other types stay checked. A function's#[arithmetic(unchecked)]now also covers the closures and constant expressions in its body. (#1882) - Contracts with the same name in different modules of one ingot can be built and tested. When a name is shared, the artifacts are named by module path, for example
lib__a__C.bin;--contractaccepts that name, or a source name that only one contract has. Same-named tests in different modules each run their own code.settings.compilationTargetin metadata records the contract's path in its source file, such asinner::C. (#1890) - Reading a field through a raw pointer stored in a struct held in a local, such as
unsafe { holder.node.value }, no longer fails with an internal access-checking error. (#1894) - On native targets,
Bytes::word_at, andword_atandbyte_aton aMemSpanbyte source, no longer read up to 31 bytes past the end of the allocation when they reach the last bytes of a span. Such a read could fault when the allocation ended at unmapped memory. (#1903) - A contract field whose type names an associated type, such as
mut v: <Nar as Tr>::Outormut g: G<<Nar as Tr>::Out>, no longer fails to build withContractFieldTypeMismatch. (#1908) - Fixed three native bugs.
core::keccak,std::evm::keccak256andkeccak_wordsof data known only at runtime no longer fail to compile. A failed allocation now reverts withPanic(0x41)instead of trapping.std::evm::packedno longer truncates sizes above 64 bits, which let apacked_widthof2**64write past its allocation. (#1909) - A use of an associated const found through a bound with an associated type binding, such as
T::LENunderT: Len<Item = u8>, now matches the same constant reached another way. Array lengths built from such a use no longer fail with "expected[u8; T::LEN], but[u8; T::LEN]is given". (#1910) - Const bodies in a generic parameter's default no longer crash the compiler when the owner has a bound naming that parameter or a later one, such as
T: Has<1>fortrait Has<const N: usize, const L: usize = { N }>, or when the body is nested in the default, such as the array length inconst L: usize = { let a: [u8; { N + 1 }] = [0; N + 1]; N + 1 }. A default that reads something only a bound on a later parameter provides is now rejected where it is declared. (#1910) std::evm::byteandsignextend, and the wrapping methods ofstd::abi::solsigned integers, no longer fail to compile on native targets. (#1911)- Using a pointer to a type that grows without bound, such as
*Grow<u256>forstruct Grow<T> { next: *Grow<[T; 1]> }, as a value now reports only the type's definition error instead of overflowing the compiler's stack. (#1912) decode_base64,decode_base58,String<N>ABI decoding,checksum_address,std::evm::rlp,std::evm::merkle,mapping_slot,keccak_packedandclone_initcodeno longer leak a scratch buffer on native targets. (#1914)- An associated type binding on anything but a trait, such as
take<Item = u8>()orlet _: G<Item = u8>, is now reported instead of silently accepted. (#1917) - An invalid type nested in a written type, such as
W<G<W>>, is now reported once, where it's written, instead of again for each type around it. (#1917) - A name that two glob imports bring in is now reported as ambiguous instead of as not found in paths with generic arguments, such as
dup<u8>(), and in generic arguments, such asK<N>. (#1917) - Generic arguments written in a body's paths and method calls are now checked as written types are.
take<Missing>()and<G<Missing> as Tr>::f()were accepted, as was a private type intake<m::Hidden>(), andG<Missing>::mk()andW<Missing> { x: 0 }reported a missing method or an expected record instead of the argument's error. A shape argument in a written type, such asSh<(mut u256,)>, is no longer rejected. (#1917) - A conditional or
matchwith a diverging branch, such asif c { return 1 } else { zero() }, no longer gives its other branches the type!; their type is inferred from how the result is used. A binding of a value of type!, such aslet w = if c { return 1 } else { return 2 }, takes its type from its uses too. (#1923) - A parameter of a known product shape, such as
_ s: Pair::Step<ref>withshape Step = (own u256, mut? u256), can now be passed on, returned or captured by a closure whole instead of crashing the compiler. A closure copies its parts' values, and rejects a non-Copypart behind an access. (#1926) - A call whose effect row depends on which impl a string literal picks, such as
fire(make(", "))wheremakeis generic over a trait implemented for bothString<N>andDynString, no longer fails with a false "missing effect row" error (8-0106). (#1928) - A
reformutbinding of a part of a named product parameter now opens an access, as a binding on any place does.let (a, mut b) = s; let (c, mut d) = swith overlapping uses, andlet (a, mut b) = s; bump(s); b += a, are now access conflicts instead of being accepted.let x = ref s.1andlet (ref x, ref y) = s.1no longer fail with an internal error, nor doeslet i = s.1for aCopystruct behind amutpart. Reading a non-Copypart withlet c = s.1now moves it, as through any access. (#1929) - A
mutpattern binding of a packed storage field now writes to the field when the pattern is on a projection's result (let Small { mut a, .. } = s.me(), or in amatch,if letorfor) or is a refutableif let,let elseorwhile let; the writes were silently lost. A projection called after&&in a condition now runs the code after itsyield, which was skipped. (#1933) - An access granted by a projection in storage can now be passed through a function that returns it, as in
mut id(mut p.a_mut())ormut pick(p.both()); this failed in MIR lowering with "the access checker places result component .. in Memory". (#1933) ?in a method of a generic type that returns anOptionorResultof an access, such asfn get(mut self) -> Option<mut T>, no longer fails with an internal access checking error (16-0004). (#1934)- A method called on the result of
?now opens the receiver's projection after the call's arguments, as it does for other receivers:cells.get(i)?.set(next(mut i))gets the cell at theithatnextleaves, and ifgetreturnsNone, the arguments run before the early exit. (#1938) - Reading an enum from storage, transient storage, calldata or raw memory whose tag names no variant now reverts with
Panic(0x21), as in Solidity. It used to reach anINVALIDinstruction, which consumes all remaining gas. (#1941) - The
mutpart of a product parameter, such ass.1fors: Pair::Step<mut>withshape Step = (own u256, mut? u256), can now be assigned (s.1 = v,s.1 += v) and passed asmut s.1, as destructuring withlet (_, mut b) = salready allowed. These were rejected with 8-0018 and 8-0066. Owned andrefparts stay read-only, and the error now names them instead of suggestingvar s. (#1944) StoragePackedArrayno longer writes outside the array. Wordwof the array at slotsnow lies atkeccak256(w ++ s), the entrywof aStorageMap<u256, u256>ats, as in Solady'sLibMapand OpenZeppelin'sBitMaps. It used to lie atkeccak256(s) + w, so a large index wrapped around the slot space and could overwrite another contract field or a map's entries. This changes the array's storage layout: values written by an earlier release are not found at their new slots. (#1948)- An
if letorwhile letpattern with|alternatives can now bind an access or a non-Copyvalue, as inif let Some((0 | 1, mut x)) = p.get()orif let (Some(t), Kind::A | Kind::B(0)) = make(). These failed with an internal access checking error (16-0004) or a use-after-move error. (#1949) - Report known non-scalar
core::hint::black_boxarguments during type checking, with a source location, instead of failing later during code generation. (#1955) - Warn when
fe buildemits runtime code or initcode exceeding the EIP-170 or EIP-3860 deployment limits. (#1956) - Fix an internal access-checking error when storing whole array rows whose lengths are const-function expressions of const generics. (#1957)
- Report unsupported fixed-array event and custom-error fields with array-specific guidance instead of the tuple hint. (#1958)
- Explain when a name in a type-level const expression resolves to a runtime parameter, including guidance for parameter/function name clashes. (#1959)
- A recv arm field whose pattern destructures, such as
V { t: (x, y) }orV { pair: Pair { n, .. } }, binds what the pattern names; before, compiling the arm failed with internal error 16-0004. (#1970) - A
let ... elsewhoseelseblock contains anif, a loop or another nested scope keeps the pattern's bindings; before, using them after the statement reported "undefined variable". - A contract field whose layout fails, such as one of an unresolved type, no longer makes every other field an unknown effect: only the bad field's error is reported, and the other fields still resolve in
usesclauses and handler bodies. - A fixed array index out of bounds now panics natively with
index out of bounds, notreverted without data. - A method call that a trait's impls tell apart by their trait arguments, such as
c.get(true)withGet<u256>andGet<bool>impls, can now be an operator's operand; it failed with "type must be known" unless its result was annotated. - A msg variant or
#[abi]struct field named like a constant in scope now decodes to its value. The decoded variant held the constant instead, so an armV { n: x }got the constant inx, and an#[abi]struct was rejected with a refutableleton its declaration. - A negative integer constant in a pattern now matches its value. With
const NEG: i256 = -2, the patternNEGmatched2and not-2. - A non-exhaustive
matchon a record variant or struct now shows which fields the uncovered values have, such asNot covered: `Shape::Rect { wide: false, .. }`, instead of onlyShape::Rect { .. }. - A pattern naming a constant that isn't an integer or
bool, such as a struct, enum or string constant, is now an error (8-0154), as is one whose value depends on generic parameters, likeT::C(8-0155). A constant that fails to evaluate is now reported at each pattern that names it, likeG<0>::Cwithconst C: u256 = 1 / N; one whose definition is invalid or depends on the pattern itself is 8-0156. In amatchthese patterns failed later in compilation. In alet, a parameter or a recv arm they were accepted and matched anything: a recv armV { pair: FIXED }ran for everyV. - A recv arm whose pattern may not match every value of its variant is now an error (8-0153). With a constant
nin scope, the armV { n, k }ran for everyV, and itsnwas the constant instead of the field. A literal field pattern, which the parser rejected, is reported by this check. - A string literal on the left of an operator, as in
"x" == s, takes its type from the operator's impl instead of failing with "type must be known". - A tuple-variant or record pattern whose name doesn't resolve is now an error.
Bar(y) => ..with noBarin scope was accepted and then crashed the backend, andFoo { a } => ..was accepted whenever the matched value's type was a struct namedFoo, even with that struct out of scope. A pattern naming an ambiguous variant, such asSome(x)with both a glob-imported and the prelude'sSome, reports the ambiguity, and the field bindings of an unresolved record pattern no longer also report themselves as undefined. A bare name in a pattern that is ambiguous, such asNoneimported both by a glob and by the prelude, is reported as ambiguous instead of becoming a binding that matches anything. A unit pattern whose path names a method, likeS::m, is reported instead of failing later in compilation. - An integer literal that does not fit its type, such as
let x: u8 = 300or a256pattern matched against au8, is now rejected; it was silently truncated. As in Rust, ausizeorisizeliteral must fit the type on the target being compiled for:0x100000000is ausizeon the EVM and native targets, but not on WebAssembly. - Fix a native heap overflow when ABI-encoding an error or message with no fields.
- Native code now loads and stores
std::evmmemory words big-endian, as the EVM does. A custom error's selector was lost from native revert data, and an encoded string literal had a garbled length. Result::unwrapnow works natively on error types without a Sol encoding, such asstd::io::Error, and panics without revert data. On EVM,fe checkreports such a call at its source location instead of failing during code generation.core::panic_codeno longer crashes unoptimized native programs withSIGSEGV: it wrote its revert data to address 0.fe checkreports a nativemainwith the wrong return type, andexternor#[unsafe(export)]functions with types C can't pass, at their source locations.fe check,fe testandfe buildnow report type errors in the built-incoreandstdingots when the code being compiled reaches the affected function, for example while editingingots/corewith a debug build offe. Previously such errors surfaced as an internal error like "semantic body is blocked by upstream causes" or "cannot structurally repacku256asusize", orfe checkpassed and only code generation failed. The real diagnostics are now shown under "Errors in dependency" with the file incoreorstd.
Performance improvements
- On native targets,
core::num::leading_zeroscompiles to a count-leading-zeros instruction on the highest nonzero 64-bit limb instead of an eight-step 256-bit shift ladder;trailing_zerosuses it too. fevm's CLZ handler shrinks from 578 instructions to 61. (#1895) MemBufferappends are faster, and growth takes the larger of double the capacity and the request, so a large append no longer over-allocates.MemVecgrows the same way.Stringlen,Displayand comparison with a span no longer work a byte at a time. (#1915)- Native code keeps panics and other cold paths out of hot loops, including during register allocation, and tests small sets of constants with a bit mask instead of a jump table. On EVM, an allocation that is never read is now removed even when the function also uses scratch memory, as panic paths do. (#1921)
- Native copies of enums that mix narrow and word-sized payloads no longer stall on the word they share. Destroying a large JSON document of numbers takes about 30% less time. (#1930)
- Assigning over an enum in memory, such as
list[i] = x, reads only its tag to find what to destroy, instead of copying the enum for each variant it tests. (#1930) - Checking instantiation limits no longer slows down superlinearly in programs with large cycles of mutually recursive functions, such as an interpreter whose opcode handlers dispatch to one another. fevm's conformance driver builds in 20 s and 4.7 GB of memory instead of 55 s and 13.3 GB. (#1960)
- A local array read at a computed index, such as
block[i]in a loop, is now stored in memory once, where it is defined. Each read used to copy the whole array into a new allocation first. (#1961) - On native targets, values passed to view parameters and read-only effects are copied less often. (#1963)
core::text::base64reads each three-byte group at once and looks digits up in a table, so encoding on EVM costs about 65 gas per input byte instead of 284. (#1968)- On native targets, including SP1,
usize,isize, raw memory pointers and references are 64 bits, the width of the target's addresses; they were 256-bit words, so every index, length and pointer was a four-limb value. A loop counting inusizeruns about 30 times faster, and aMemVecscan about 45 times faster.MemVecandMemBuffertake 24 bytes andBox8, and amain(argc: i32, argv: **u8)receives the host'sargvwithout a copy.
Deprecations and Removals
- Memory owners now destroy what they hold, which changes the meaning of some existing code.
v[i] = xandsetdestroy the element they overwrite, so fill the uninitialized elements ofMemArray::new_uninitorcore::ptr::alloc_arraythrough raw places (*array.ptr_at(i) = x). On native targets, a pointer into an owner dangles once the owner is destroyed: keep the owner alive, orcore::mem::forgetit.MemArray::into_bytesandinto_bufferrequireCopyelements.std::native::ByteBufferandFixedStackare removed in favor ofMemBufferandMemVec, which need noreleasecall: indexing replacesbyte_atandset_byte, andcopy_withinandcopy_fromare onbuffer.span()and its slices. (#1870) - In
#[arithmetic(unchecked)]code, an overflowing operator on a type parameter or associated type requires its wrapping trait:a + bonT: Addis now an error, and needsT: WrappingAddto wrap, ora.add(b)to stay checked. Where the mode comes fromfe.toml, such an operator without the bound callsAdd. It used to wrap when instantiated at an integer, asx.add(y)on an integer did in unchecked code; both now panic on overflow. (#1882) WrappingAdd,WrappingSubandWrappingMultake their operandsown, asAdd,SubandMuldo, so an operator moves the same operands in checked and unchecked code. Implementations changefn wrapping_add(self, _ other: T)tofn wrapping_add(own self, _ other: own T). (#1882)- An SP1 guest that allocates now fails to link instead of failing to compile, because SP1's runtime has no
malloc. (#1913) DynString,BytesandDynArrayown their allocations and free them when dropped, so they no longer leak on native targets. They are no longerCopy: useclone()for a second value, or a view parameter to read one without moving it.from_abi_tailnow takes ownership of the allocation it is given, so a pointer into another owner, such as aMemBuffer, must be copied first.DynString::as_bytes, whose result outlived the string, is replaced byinto_bytes, which takes the string and keeps its allocation.PackedUserOperationis no longerCopy, and functions that send aBytesorDynArrayin a message, such as the ERC-721 and ERC-1155 helpers, take it byown.DynArray::from_sliceanddyn_array_fromno longer needT: Copy.Encode::encodeandencode_fieldread their value through a view, so an implementation writesfn encode(self, _ out: AbiFrame)in place ofown self, and binds a tuple's parts byref. Fixed arrays encode and decode withoutT: Copy, so#[abi],#[event]and#[error]structs can hold fixed arrays of non-Copyelements.std::evm::clones::clone_initcodereturns aFixedMemBuffer<55>that owns the code, in place of a span of memory that nothing owned. (#1914)- Renamed
DynString::payload_spanandBytes::payload_spantospan, matchingMemBuffer,MemArrayandArg. (#1928) - Calling a function declared in an
externblock now requires anunsafeblock or function, unless the declaration is aconst fnor belongs tocoreorstd: nothing checks what foreign code does, so a call such asmemset(p, 0, n)could corrupt memory from safe code. - Only the root module's
pub fn main(), without parameters, is a program entry, on every target. A single-file EVM build no longer enters its other parameterless functions, so a file without contracts orpub fn main()builds nothing, and amainthat is notpub, takes parameters or lies in an inlinemodis an ordinary function. MemBuffer::allocandMemBuffer::alloc_uninitare renamedMemBuffer::newandMemBuffer::new_uninit.ascasts must be lossless on every target, whereusizeandisizetake the target's width (32 bits on WebAssembly, 64 on native targets, 256 on EVM):u8,u16andu32convert tousize,usizetou256, and a memory pointer to and fromusize, butu64 as usize,u256 as usize,usize as u32andusize as u64are rejected. Usedowncast(),downcast_saturate()ordowncast_unchecked()instead. A state pointer such asStorPtr<T>still converts to and from itsu256slot.core::size_ofreturnsusize, andcore::ptr's allocation sizes, copy lengths and offsets (alloc_bytes,offset,offset_bytes,zero_bytes,copy_raw) areusize.std::io's character I/O is gone:HostIo,host(), theReadandWritetraits,read_char,write_char,write_bytes,writeandwriteln. Use theStdout,StderrandStdineffects withprint!,println!orStdinReaderinstead. A nativemaintakes no parameters:pub fn main(argc: i32, argv: **u8)andstd::native::Args::neware gone; declareuses (args: Args)fromstd::envinstead.