feat: add display UI with splash screen, dashboard, and RSSI sparkline

Implement full SSD1306 OLED display support:
- Splash screen on boot (1.5s) with project name, version, board
- Dashboard showing radio state, config, counters, RSSI/SNR
- RSSI history sparkline (64-sample ring buffer, 2px-wide bars)
- DisplayOn/DisplayOff host commands routed via dedicated channel
- Pure render functions against generic DrawTarget (no hardware coupling)
- Display on/off control bypasses radio task entirely

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
This commit is contained in:
swaitsandClaude Opus 4.6 committed 2026-03-29 20:42:54 -06:00
1 parent 7064110293
commit 3102fc747a
17624 files changed
+4564927 -37

No files matched your search

+1
View File
@@ -1,2 +1,3 @@
/target
/vendor
src/board/mod.rs
Generated
+149 -4
View File
@@ -28,9 +28,9 @@ checksum = "c08606f8c3cbf4ce6ec8e28fb0014a2c086708fe954eaa885384a6165172e7e8"
[[package]]
name = "az"
version = "1.3.0"
version = "1.2.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "be5eb007b7cacc6c660343e96f650fedf4b5a77512399eb952ca6642cf8d13f7"
checksum = "7b7e4c2464d97fe331d41de9d5db0def0a96f4d823b8b32a2efd503578988973"
[[package]]
name = "bare-metal"
@@ -86,6 +86,12 @@ version = "2.11.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "843867be96c8daad0d758b57df9392b6d8d271134fce549de6ce169ff98a92af"
[[package]]
name = "byte-slice-cast"
version = "1.2.3"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "7575182f7272186991736b70173b0ea045398f984bf5ebbb3804736ce1330c9d"
[[package]]
name = "bytemuck"
version = "1.25.0"
@@ -287,6 +293,35 @@ dependencies = [
"syn 2.0.117",
]
[[package]]
name = "display-interface"
version = "0.5.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "7ba2aab1ef3793e6f7804162debb5ac5edb93b3d650fbcc5aeb72fcd0e6c03a0"
[[package]]
name = "display-interface-i2c"
version = "0.5.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "0d964fa85bbbb5a6ecd06e58699407ac5dc3e3ad72dac0ab7e6b0d00a1cd262d"
dependencies = [
"display-interface",
"embedded-hal 1.0.0",
"embedded-hal-async",
]
[[package]]
name = "display-interface-spi"
version = "0.5.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "f86b9ec30048b1955da2038fcc3c017f419ab21bb0001879d16c0a3749dc6b7a"
dependencies = [
"byte-slice-cast",
"display-interface",
"embedded-hal 1.0.0",
"embedded-hal-async",
]
[[package]]
name = "document-features"
version = "0.2.12"
@@ -535,6 +570,29 @@ dependencies = [
"nb 1.1.0",
]
[[package]]
name = "embedded-graphics"
version = "0.8.2"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "4e8da660bb0c829b34a56a965490597f82a55e767b91f9543be80ce8ccb416fe"
dependencies = [
"az",
"byteorder",
"embedded-graphics-core",
"float-cmp",
"micromath",
]
[[package]]
name = "embedded-graphics-core"
version = "0.4.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "95743bef3ff70fcba3930246c4e6872882bbea0dcc6da2ca860112e0cd4bd09f"
dependencies = [
"az",
"byteorder",
]
[[package]]
name = "embedded-hal"
version = "0.2.7"
@@ -823,9 +881,9 @@ dependencies = [
[[package]]
name = "fixed"
version = "1.31.0"
version = "1.29.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "9af2cbf772fa6d1c11358f92ef554cb6b386201210bcf0e91fb7fba8a907fb40"
checksum = "707070ccf8c4173548210893a0186e29c266901b71ed20cd9e2ca0193dfe95c3"
dependencies = [
"az",
"bytemuck",
@@ -833,6 +891,15 @@ dependencies = [
"typenum",
]
[[package]]
name = "float-cmp"
version = "0.9.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "98de4bbd547a563b716d8dfa9aad1cb19bfab00f4fa09a6a4ed21dbcf44ce9c4"
dependencies = [
"num-traits",
]
[[package]]
name = "fnv"
version = "1.0.7"
@@ -991,10 +1058,12 @@ dependencies = [
"defmt 0.3.100",
"embassy-embedded-hal",
"embassy-executor",
"embassy-futures",
"embassy-nrf",
"embassy-sync 0.6.2",
"embassy-time",
"embassy-usb",
"embedded-graphics",
"esp-backtrace",
"esp-hal",
"esp-hal-embassy",
@@ -1006,6 +1075,7 @@ dependencies = [
"panic-probe",
"postcard",
"serde",
"ssd1306",
"static_cell",
]
@@ -1028,6 +1098,19 @@ dependencies = [
"num-traits",
]
[[package]]
name = "maybe-async-cfg"
version = "0.2.4"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "a1e083394889336bc66a4eaf1011ffbfa74893e910f902a9f271fa624c61e1b2"
dependencies = [
"proc-macro-error",
"proc-macro2",
"pulldown-cmark",
"quote",
"syn 1.0.109",
]
[[package]]
name = "memchr"
version = "2.8.0"
@@ -1040,6 +1123,12 @@ version = "0.3.3"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "38d1115007560874e373613744c6fba374c17688327a71c1476d1a5954cc857b"
[[package]]
name = "micromath"
version = "2.1.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "c3c8dda44ff03a2f238717214da50f65d5a53b45cd213a7370424ffdb6fae815"
[[package]]
name = "minijinja"
version = "2.18.0"
@@ -1280,6 +1369,30 @@ dependencies = [
"toml_edit 0.25.8+spec-1.1.0",
]
[[package]]
name = "proc-macro-error"
version = "1.0.4"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "da25490ff9892aab3fcf7c36f08cfb902dd3e71ca0f9f9517bea02a73a5ce38c"
dependencies = [
"proc-macro-error-attr",
"proc-macro2",
"quote",
"syn 1.0.109",
"version_check",
]
[[package]]
name = "proc-macro-error-attr"
version = "1.0.4"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "a1be40180e52ecc98ad80b184934baf3d0d29f979574e439af5a55274b35f869"
dependencies = [
"proc-macro2",
"quote",
"version_check",
]
[[package]]
name = "proc-macro-error-attr2"
version = "2.0.0"
@@ -1311,6 +1424,17 @@ dependencies = [
"unicode-ident",
]
[[package]]
name = "pulldown-cmark"
version = "0.11.3"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "679341d22c78c6c649893cbd6c3278dcbe9fc4faa62fea3a9296ae2b50c14625"
dependencies = [
"bitflags 2.11.0",
"memchr",
"unicase",
]
[[package]]
name = "quote"
version = "1.0.45"
@@ -1469,6 +1593,21 @@ dependencies = [
"lock_api",
]
[[package]]
name = "ssd1306"
version = "0.10.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "3ea6aac2d078bbc71d9b8ac3f657335311f3b6625e9a1a96ccc29f5abfa77c56"
dependencies = [
"display-interface",
"display-interface-i2c",
"display-interface-spi",
"embedded-graphics-core",
"embedded-hal 1.0.0",
"embedded-hal-async",
"maybe-async-cfg",
]
[[package]]
name = "ssmarshal"
version = "1.0.0"
@@ -1656,6 +1795,12 @@ version = "0.1.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "e87a2ed6b42ec5e28cc3b94c09982969e9227600b2e3dcbc1db927a84c06bd69"
[[package]]
name = "unicase"
version = "2.9.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "dbc4bc3a9f746d862c45cb89d705aa10f187bb96c76001afab07a0d35ce60142"
[[package]]
name = "unicode-ident"
version = "1.0.24"
+8 -1
View File
@@ -19,7 +19,14 @@ postcard = { version = "1.0", default-features = false }
serde = { version = "1.0", default-features = false, features = ["derive"] }
static_cell = "2.1"
# ESP32-S3 (Heltec V3) — builds with nightly + -Zbuild-std=core
# Display rendering
embedded-graphics = "0.8"
ssd1306 = { version = "0.10", features = ["async"] }
# Async combinators (select)
embassy-futures = "0.1"
# ESP32-S3 (Heltec V3) — requires esp toolchain (espup)
esp-backtrace = { version = "0.14", optional = true, features = ["esp32s3", "panic-handler", "defmt"] }
esp-hal = { version = "0.22", optional = true, features = ["esp32s3"] }
esp-hal-embassy = { version = "0.5", optional = true, features = ["esp32s3"] }
+3 -3
View File
@@ -1,8 +1,8 @@
set shell := ["bash", "-c"]
# Board definitions: feature target chip [extra-cargo-args]
# Boards requiring the esp toolchain are marked — check-all skips them gracefully
# if the toolchain isn't installed.
# Board definitions: feature target chip
# Xtensa targets require the esp toolchain (espup); check-all gracefully skips
# boards whose toolchain isn't installed.
heltec_v3 := "heltec_v3 xtensa-esp32s3-none-elf esp32s3"
rak_4631 := "rak_4631 thumbv7em-none-eabihf nRF52840_xxAA"
+11
View File
@@ -4,6 +4,17 @@ use embassy_sync::watch::Watch;
use crate::protocol::{Command, RadioConfig, Response};
/// Display commands routed from usb_task to display_task.
#[derive(Debug, Clone, Copy, defmt::Format)]
#[allow(dead_code)] // Constructed by usb_task once CDC-ACM is wired up
pub enum DisplayCommand {
On,
Off,
}
/// USB-to-display command channel.
pub type DisplayCommandChannel = Channel<CriticalSectionRawMutex, DisplayCommand, 4>;
/// Host-to-radio command channel.
pub type CommandChannel = Channel<CriticalSectionRawMutex, Command, 16>;
+217 -13
View File
@@ -1,15 +1,219 @@
use crate::channel::RadioStatus;
use core::fmt::Write;
/// Render the status dashboard to a framebuffer.
///
/// Pure logic — no hardware dependency. Takes a status snapshot and produces
/// draw calls that the display task flushes to the screen.
pub fn dashboard(status: &RadioStatus) {
// TODO: use embedded-graphics to render:
// Line 1: board name + radio state
// Line 2: freq / BW / SF
// Line 3: TX power / coding rate
// Line 4: RX count / TX count
// Line 5: last RSSI / SNR
let _ = status;
use embedded_graphics::mono_font::ascii::{FONT_6X10, FONT_9X15_BOLD};
use embedded_graphics::mono_font::MonoTextStyle;
use embedded_graphics::pixelcolor::BinaryColor;
use embedded_graphics::prelude::*;
use embedded_graphics::primitives::{Line, PrimitiveStyle, Rectangle};
use embedded_graphics::text::renderer::CharacterStyle;
use embedded_graphics::text::{Alignment, Text};
use heapless::String;
use crate::channel::{RadioState, RadioStatus};
use crate::protocol::Bandwidth;
/// RSSI ring buffer length — one bar per 2px across 128px.
pub const RSSI_HISTORY_LEN: usize = 64;
// Layout constants
const W: i32 = 128;
const FONT_H: i32 = 10;
const SEP_Y: i32 = 40;
const SPARK_TOP: i32 = 42;
const SPARK_H: i32 = 22; // y=42..63
// RSSI mapping range (dBm)
const RSSI_MIN: i16 = -120;
const RSSI_MAX: i16 = 0;
/// Render the boot splash screen.
pub fn splash(
target: &mut impl DrawTarget<Color = BinaryColor>,
board_name: &str,
version: &str,
) {
let _ = target.clear(BinaryColor::Off);
let title_style = MonoTextStyle::new(&FONT_9X15_BOLD, BinaryColor::On);
let sub_style = MonoTextStyle::new(&FONT_6X10, BinaryColor::On);
// Title centered vertically and horizontally
Text::with_alignment("LoRa Dongle", Point::new(W / 2, 24), title_style, Alignment::Center)
.draw(target)
.ok();
// Version
Text::with_alignment(version, Point::new(W / 2, 42), sub_style, Alignment::Center)
.draw(target)
.ok();
// Board name
Text::with_alignment(board_name, Point::new(W / 2, 54), sub_style, Alignment::Center)
.draw(target)
.ok();
}
/// Render the main status dashboard.
pub fn dashboard(
target: &mut impl DrawTarget<Color = BinaryColor>,
status: &RadioStatus,
rssi_history: &[i16; RSSI_HISTORY_LEN],
rssi_count: usize,
) {
let _ = target.clear(BinaryColor::Off);
let style = MonoTextStyle::new(&FONT_6X10, BinaryColor::On);
match status.config {
Some(cfg) => {
// Row 0: state + frequency
let mut buf: String<21> = String::new();
let state_str = match status.state {
RadioState::Idle => "IDLE",
RadioState::Receiving => "RX",
RadioState::Transmitting => "TX",
};
let freq_whole = cfg.freq_hz / 1_000_000;
let freq_frac = (cfg.freq_hz % 1_000_000) / 100_000;
let _ = write!(buf, "{:<4} {}.{}MHz", state_str, freq_whole, freq_frac);
Text::new(&buf, Point::new(0, FONT_H - 1), style)
.draw(target)
.ok();
// Row 0: state indicator — inverted box behind state text
if status.state != RadioState::Idle {
let state_w = state_str.len() as i32 * 6;
let mut inv_style = MonoTextStyle::new(&FONT_6X10, BinaryColor::Off);
inv_style.set_background_color(Some(BinaryColor::On));
Rectangle::new(Point::new(0, 0), Size::new(state_w as u32, FONT_H as u32))
.into_styled(PrimitiveStyle::with_fill(BinaryColor::On))
.draw(target)
.ok();
Text::new(state_str, Point::new(0, FONT_H - 1), inv_style)
.draw(target)
.ok();
}
// Row 1: BW SF CR
buf.clear();
let bw_str = match cfg.bw {
Bandwidth::Khz7 => "7k",
Bandwidth::Khz10 => "10k",
Bandwidth::Khz15 => "15k",
Bandwidth::Khz20 => "20k",
Bandwidth::Khz31 => "31k",
Bandwidth::Khz41 => "41k",
Bandwidth::Khz62 => "62k",
Bandwidth::Khz125 => "125k",
Bandwidth::Khz250 => "250k",
Bandwidth::Khz500 => "500k",
};
let _ = write!(buf, "BW{} SF{} CR4/{}", bw_str, cfg.sf, cr_denom(cfg.cr));
Text::new(&buf, Point::new(0, FONT_H * 2 - 1), style)
.draw(target)
.ok();
// Row 2: TX power + counters
buf.clear();
let _ = write!(
buf,
"{}dBm RX:{} TX:{}",
cfg.tx_power_dbm, status.rx_count, status.tx_count
);
Text::new(&buf, Point::new(0, FONT_H * 3 - 1), style)
.draw(target)
.ok();
// Row 3: RSSI + SNR
buf.clear();
match (status.last_rssi, status.last_snr) {
(Some(rssi), Some(snr)) => {
let _ = write!(buf, "RSSI:{} SNR:{}", rssi, snr);
}
(Some(rssi), None) => {
let _ = write!(buf, "RSSI:{}", rssi);
}
_ => {
let _ = write!(buf, "No signal");
}
}
Text::new(&buf, Point::new(0, FONT_H * 4 - 1), style)
.draw(target)
.ok();
// Separator line
Line::new(Point::new(0, SEP_Y), Point::new(W - 1, SEP_Y))
.into_styled(PrimitiveStyle::with_stroke(BinaryColor::On, 1))
.draw(target)
.ok();
// RSSI sparkline
rssi_sparkline(target, rssi_history, rssi_count);
}
None => {
Text::new("IDLE", Point::new(0, FONT_H - 1), style)
.draw(target)
.ok();
Text::new("Waiting for host...", Point::new(0, FONT_H * 3 - 1), style)
.draw(target)
.ok();
}
}
}
/// Render the RSSI history as a bar-chart sparkline.
fn rssi_sparkline(
target: &mut impl DrawTarget<Color = BinaryColor>,
history: &[i16; RSSI_HISTORY_LEN],
count: usize,
) {
if count == 0 {
return;
}
let n = count.min(RSSI_HISTORY_LEN);
let fill = PrimitiveStyle::with_fill(BinaryColor::On);
for i in 0..n {
// Read from oldest to newest for left-to-right display.
// history is stored with newest at index count-1 (modular).
// We want bar 0 = oldest visible, bar n-1 = newest.
let idx = if count <= RSSI_HISTORY_LEN {
i
} else {
(count - RSSI_HISTORY_LEN + i) % RSSI_HISTORY_LEN
};
let rssi = history[idx].clamp(RSSI_MIN, RSSI_MAX);
// Map to bar height: 0 at RSSI_MIN, SPARK_H at RSSI_MAX
let bar_h = ((rssi - RSSI_MIN) as i32 * SPARK_H) / (RSSI_MAX - RSSI_MIN) as i32;
if bar_h == 0 {
continue;
}
let x = (RSSI_HISTORY_LEN - n + i) as i32 * 2; // right-align bars
let y = SPARK_TOP + SPARK_H - bar_h;
Rectangle::new(
Point::new(x, y),
Size::new(2, bar_h as u32),
)
.into_styled(fill)
.draw(target)
.ok();
}
}
/// Clear the display (display-off state).
pub fn blank(target: &mut impl DrawTarget<Color = BinaryColor>) {
let _ = target.clear(BinaryColor::Off);
}
fn cr_denom(cr: crate::protocol::CodingRate) -> u8 {
use crate::protocol::CodingRate;
match cr {
CodingRate::Cr4_5 => 5,
CodingRate::Cr4_6 => 6,
CodingRate::Cr4_7 => 7,
CodingRate::Cr4_8 => 8,
}
}
+104 -11
View File
@@ -1,22 +1,115 @@
use embassy_executor::task;
use embassy_futures::select::{select, Either};
use embassy_time::Timer;
use ssd1306::mode::DisplayConfig;
use ssd1306::prelude::DisplayRotation;
use ssd1306::size::DisplaySize128x64;
use ssd1306::{I2CDisplayInterface, Ssd1306};
use crate::board::DisplayParts;
use crate::channel::StatusWatch;
use crate::channel::{DisplayCommand, DisplayCommandChannel, StatusWatch};
use super::render;
use super::render::{self, RSSI_HISTORY_LEN};
#[task]
pub async fn display_task(parts: DisplayParts, status: &'static StatusWatch) {
let _i2c = parts.i2c;
const BOARD_NAME: &str = if cfg!(feature = "rak_4631") {
"rak_4631"
} else if cfg!(feature = "heltec_v3") {
"heltec_v3"
} else {
"unknown"
};
// TODO: init SSD1306 driver from I2C handle
// TODO: initial render of empty dashboard
struct DisplayState {
rssi_history: [i16; RSSI_HISTORY_LEN],
rssi_count: usize,
last_rx_count: u32,
display_on: bool,
}
let mut receiver = status.receiver().unwrap();
impl DisplayState {
fn new() -> Self {
Self {
rssi_history: [0; RSSI_HISTORY_LEN],
rssi_count: 0,
last_rx_count: 0,
display_on: true,
}
}
fn push_rssi(&mut self, rssi: i16) {
let idx = self.rssi_count % RSSI_HISTORY_LEN;
self.rssi_history[idx] = rssi;
self.rssi_count += 1;
}
}
#[task]
pub async fn display_task(
parts: DisplayParts,
status: &'static StatusWatch,
display_commands: &'static DisplayCommandChannel,
) {
let interface = I2CDisplayInterface::new(parts.i2c);
let mut display = Ssd1306::new(interface, DisplaySize128x64, DisplayRotation::Rotate0)
.into_buffered_graphics_mode();
if display.init().is_err() {
defmt::error!("SSD1306 init failed");
return;
}
// Splash screen
render::splash(&mut display, BOARD_NAME, env!("CARGO_PKG_VERSION"));
let _ = display.flush();
Timer::after_millis(1500).await;
// Dashboard
let mut state = DisplayState::new();
let mut receiver = status.receiver().unwrap();
// Initial empty dashboard
render::dashboard(
&mut display,
&crate::channel::RadioStatus::default(),
&state.rssi_history,
state.rssi_count,
);
let _ = display.flush();
loop {
let state = receiver.changed().await;
render::dashboard(&state);
// TODO: flush framebuffer to display
match select(receiver.changed(), display_commands.receive()).await {
Either::First(radio_status) => {
if !state.display_on {
continue;
}
// Push new RSSI sample only when rx_count advances
if radio_status.rx_count != state.last_rx_count {
state.last_rx_count = radio_status.rx_count;
if let Some(rssi) = radio_status.last_rssi {
state.push_rssi(rssi);
}
}
render::dashboard(
&mut display,
&radio_status,
&state.rssi_history,
state.rssi_count,
);
let _ = display.flush();
}
Either::Second(cmd) => match cmd {
DisplayCommand::Off => {
state.display_on = false;
render::blank(&mut display);
let _ = display.flush();
}
DisplayCommand::On => {
state.display_on = true;
// Will re-render on next status update
}
},
}
}
}
+6 -3
View File
@@ -10,7 +10,7 @@ mod usb;
use embassy_executor::Spawner;
use crate::channel::{CommandChannel, ResponseChannel, StatusWatch};
use crate::channel::{CommandChannel, DisplayCommandChannel, ResponseChannel, StatusWatch};
cfg_if::cfg_if! {
if #[cfg(feature = "rak_4631")] {
@@ -23,16 +23,19 @@ cfg_if::cfg_if! {
static COMMANDS: CommandChannel = CommandChannel::new();
static RESPONSES: ResponseChannel = ResponseChannel::new();
static STATUS: StatusWatch = StatusWatch::new();
static DISPLAY_COMMANDS: DisplayCommandChannel = DisplayCommandChannel::new();
#[embassy_executor::main]
async fn main(spawner: Spawner) {
let board = board::Board::init();
let (radio, usb, display) = board.into_parts();
let has_display = display.is_some();
spawner.spawn(radio::radio_task(radio, &COMMANDS, &RESPONSES, &STATUS)).unwrap();
spawner.spawn(usb::usb_task(usb, &COMMANDS, &RESPONSES)).unwrap();
spawner.spawn(usb::usb_task(usb, &COMMANDS, &RESPONSES, &DISPLAY_COMMANDS, has_display)).unwrap();
if let Some(dp) = display {
spawner.spawn(display::display_task(dp, &STATUS)).unwrap();
spawner.spawn(display::display_task(dp, &STATUS, &DISPLAY_COMMANDS)).unwrap();
}
}
+4
View File
@@ -55,6 +55,8 @@ pub enum Command {
config: Option<RadioConfig>,
payload: Vec<u8, MAX_PAYLOAD>,
},
DisplayOn,
DisplayOff,
}
/// Firmware → host responses.
@@ -70,6 +72,7 @@ pub enum Response {
payload: Vec<u8, MAX_PAYLOAD>,
},
TxDone,
Ok,
Error(ErrorCode),
}
@@ -82,4 +85,5 @@ pub enum ErrorCode {
TxTimeout = 3,
CrcError = 4,
NotConfigured = 5,
NoDisplay = 6,
}
+3
View File
@@ -66,6 +66,9 @@ pub async fn radio_task(
// TODO: put lora radio into standby
status.sender().send(state.clone());
}
Command::DisplayOn | Command::DisplayOff => {
// Routed by usb_task to display — should never reach radio.
}
Command::Transmit { config, payload } => {
let tx_config = config.or(state.config);
if let Some(_cfg) = tx_config {
+23 -2
View File
@@ -1,24 +1,45 @@
use embassy_executor::task;
use crate::board::UsbParts;
use crate::channel::{CommandChannel, ResponseChannel};
use crate::channel::{CommandChannel, DisplayCommandChannel, ResponseChannel};
#[task]
pub async fn usb_task(
parts: UsbParts,
commands: &'static CommandChannel,
responses: &'static ResponseChannel,
display_commands: &'static DisplayCommandChannel,
has_display: bool,
) {
let _driver = parts.driver;
let _ = commands;
let _ = responses;
let _ = display_commands;
let _ = has_display;
// TODO: build embassy_usb CDC-ACM class from driver
// TODO: COBS-framed postcard ser/de loop:
// select! {
// bytes = cdc_read() => deserialize Command → commands.send()
// bytes = cdc_read() => {
// let cmd: Command = deserialize(bytes);
// match cmd {
// Command::DisplayOn => route_display(DisplayCommand::On),
// Command::DisplayOff => route_display(DisplayCommand::Off),
// other => commands.send(other).await,
// }
// }
// resp = responses.receive() => serialize Response → cdc_write()
// }
//
// Display command routing:
// async fn route_display(cmd, display_commands, has_display, responses) {
// if has_display {
// display_commands.send(cmd).await;
// responses.send(Response::Ok).await;
// } else {
// responses.send(Response::Error(ErrorCode::NoDisplay)).await;
// }
// }
loop {
embassy_time::Timer::after_secs(1).await;
+1
View File
@@ -0,0 +1 @@
{"files":{".cargo_vcs_info.json":"a9e489a9cb5309b1be3502e513cdf8b6d65d7f4dbd8d1b6273717e5f46d16370",".github/workflows/rust.yml":"0d0bc3c9d0fda45f18bc7dc169d90b78d2757a01f3e789fea5011c4e4d3ebd8f","Cargo.toml":"4ddbacdfae8fa9049a0f47b8f479a5e09536687917415f5c993c9c5438cbdbcd","Cargo.toml.orig":"84591ea3bef6c64be7d7b22aa3d12e3cdd723ec3afb6236c103bb497a8097dde","FAQ.md":"9eb41898523ee209a0a937f9bcb78afe45ad55ca0556f8a4d4063558098f6d1e","LICENSE-APACHE":"a60eea817514531668d7e00765731449fe14d059d3249e0bc93b36de45f759f2","LICENSE-MIT":"0444c6991eead6822f7b9102e654448d51624431119546492e8b231db42c48bb","README.md":"d7f74d616a751bcca23d5d3b58a6daf556356a526c5f0b6aa0504715d176549a","build.rs":"d7dd5428c78b80bb3c99068561641ec661f0f94defbda17f85b443e358ab6396","rustfmt.toml":"e090969e99df9360705680cc0097cfaddae10c22dc2e01470592cf3b9787fd36","src/aes_hash.rs":"c29c24588676f669c9c5d928100d5743325a8a7aa19dae9e899b7108c2bf5309","src/convert.rs":"85789aaa5da2a1d37d91570ba6fa14ef261ff264dac40a624a88178f41c5494b","src/fallback_hash.rs":"6061ff5c42423f9028f487e4b042449424da6a5de5fd57fdb5f2b8063124312d","src/hash_map.rs":"1df153463e3c1f818bebf09938bfe63f18dc34cfeaf3041a67e2a604135e3f84","src/hash_quality_test.rs":"61a5d8a24b1882479ef72590f9165a2cfd5af3a26e1aca3e172061798d93f35e","src/hash_set.rs":"222d4563ea3749bc63ab7ded5c4a4239a0a1515b6a64301c0e04cf095386776d","src/lib.rs":"de037bb4e0535fdf4c432f87f0e0d0e5ba9a4ba25033f97c9a76ded33574aa9e","src/operations.rs":"dae1c602718e29e5839fad52e512a8cf168c1d888cfa9cd074302db7dbabb3d4","src/random_state.rs":"3a28f818165289071339299c49d8deb93fb27bf31ab1416349132d3af5ec2ad4","src/specialize.rs":"f2fe0de5d79851ee8ef6d977f418db8d5df5469eaa18a5173fa16cd61df8312e","tests/bench.rs":"e84993bb03089b11b8df0dccce273eab4d7aab966a71bc609d1e220ca783f023","tests/map_tests.rs":"fd6028cc96e1892b7d27cb5f55bf290b8e331305515598b35a54a3f349c5d7a7","tests/nopanic.rs":"0d28a46248d77283941db1d9fd154c68b965c81a0e3db1fe4a43e06fc448da8f"},"package":"5a15f179cd60c4584b8a8c596927aadc462e27f2ca70c04e0071964a73ba7a75"}
+6
View File
@@ -0,0 +1,6 @@
{
"git": {
"sha1": "9aa1ba20f05ed582eda04ea625d5658c92195a57"
},
"path_in_vcs": ""
}
+156
View File
@@ -0,0 +1,156 @@
name: Rust
on: [push, pull_request]
jobs:
build:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4
- name: Install latest stable
uses: dtolnay/rust-toolchain@master
with:
toolchain: stable
components: clippy
- name: check nostd
run: cargo check --no-default-features
- name: test nostd
run: cargo test --no-default-features
- name: check constrandom
run: cargo check --no-default-features --features compile-time-rng
- name: test constrandom
run: cargo test --no-default-features --features compile-time-rng
- name: check fixed-seed
run: cargo check --no-default-features --features std
- name: check
run: cargo check
- name: test
run: cargo test
nightly:
name: nightly
runs-on: ubuntu-latest
env:
RUSTFLAGS: -C target-cpu=native
steps:
- uses: actions/checkout@v4
- name: Install latest nightly
uses: dtolnay/rust-toolchain@master
with:
toolchain: nightly
components: clippy
- name: check nightly
run: cargo check -Z msrv-policy
- name: test nightly
run: cargo test
- name: check serde
run: cargo check --features serde
- name: test serde
run: cargo test --features serde
linux_arm7:
name: Linux ARMv7
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4
- uses: dtolnay/rust-toolchain@master
with:
toolchain: stable
targets: armv7-unknown-linux-gnueabihf
- run: cargo check --target armv7-unknown-linux-gnueabihf
- name: Install 1.72.0
uses: dtolnay/rust-toolchain@master
with:
toolchain: 1.72.0
targets: armv7-unknown-linux-gnueabihf
- run: cargo +1.72.0 check --target armv7-unknown-linux-gnueabihf
aarch64-apple-darwin:
name: Aarch64 Apple Darwin
runs-on: macos-latest
steps:
- uses: actions/checkout@v4
- uses: dtolnay/rust-toolchain@master
with:
toolchain: stable
targets: aarch64-apple-darwin
- run: cargo check --target aarch64-apple-darwin
- run: cargo test
- run: cargo test --no-default-features --features compile-time-rng
- name: Install 1.72.0
uses: dtolnay/rust-toolchain@master
with:
toolchain: 1.72.0
targets: aarch64-apple-darwin
- run: cargo +1.72.0 check --target aarch64-apple-darwin
i686-unknown-linux-gnu:
name: Linux i686
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4
- uses: dtolnay/rust-toolchain@master
with:
toolchain: stable
targets: i686-unknown-linux-gnu
- name: Install cross compile tools
run: sudo apt-get install -y gcc-multilib libc6-i386 libc6-dev-i386
- run: cargo check --target i686-unknown-linux-gnu
- run: cargo test --target i686-unknown-linux-gnu
- name: check constrandom
run: cargo check --no-default-features --features compile-time-rng --target i686-unknown-linux-gnu
- name: Install 1.72.0
uses: dtolnay/rust-toolchain@master
with:
toolchain: 1.72.0
targets: i686-unknown-linux-gnu
- run: cargo +1.72.0 check --target i686-unknown-linux-gnu
- name: check constrandom
run: cargo +1.72.0 check --no-default-features --features compile-time-rng --target i686-unknown-linux-gnu
x86_64-unknown-linux-gnu:
name: Linux x86_64
runs-on: ubuntu-latest
env:
RUSTFLAGS: -C target-cpu=skylake -C target-feature=+aes
steps:
- uses: actions/checkout@v4
- uses: dtolnay/rust-toolchain@master
with:
toolchain: nightly
targets: x86_64-unknown-linux-gnu
- run: cargo check --target x86_64-unknown-linux-gnu
- run: cargo test --target x86_64-unknown-linux-gnu
- name: check constrandom
run: cargo check --no-default-features --features compile-time-rng --target x86_64-unknown-linux-gnu
- name: Install 1.72.0
uses: dtolnay/rust-toolchain@master
with:
toolchain: 1.72.0
- run: cargo +1.72.0 check --target x86_64-unknown-linux-gnu
- name: check constrandom
run: cargo +1.72.0 check --no-default-features --features compile-time-rng --target x86_64-unknown-linux-gnu
thumbv6m:
name: thumbv6m
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4
- uses: dtolnay/rust-toolchain@master
with:
toolchain: stable
targets: thumbv6m-none-eabi
- run: cargo check --target thumbv6m-none-eabi --no-default-features
wasm32-unknown-unknown:
name: wasm
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4
- uses: dtolnay/rust-toolchain@master
with:
toolchain: stable
targets: wasm32-unknown-unknown
- run: cargo check --target wasm32-unknown-unknown --no-default-features
no_std:
name: no-std build
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4
- uses: dtolnay/rust-toolchain@master
with:
toolchain: nightly
- run: cargo build --manifest-path=no_std_test/Cargo.toml
+183
View File
@@ -0,0 +1,183 @@
# THIS FILE IS AUTOMATICALLY GENERATED BY CARGO
#
# When uploading crates to the registry Cargo will automatically
# "normalize" Cargo.toml files for maximal compatibility
# with all versions of Cargo and also rewrite `path` dependencies
# to registry (e.g., crates.io) dependencies.
#
# If you are reading this file be aware that the original Cargo.toml
# will likely look very different (and much more reasonable).
# See Cargo.toml.orig for the original contents.
[package]
edition = "2018"
rust-version = "1.60.0"
name = "ahash"
version = "0.8.12"
authors = ["Tom Kaitchuck <Tom.Kaitchuck@gmail.com>"]
build = "build.rs"
exclude = [
"/smhasher",
"/benchmark_tools",
]
autobins = false
autoexamples = false
autotests = false
autobenches = false
description = "A non-cryptographic hash function using AES-NI for high performance"
documentation = "https://docs.rs/ahash"
readme = "README.md"
keywords = [
"hash",
"hasher",
"hashmap",
"aes",
"no-std",
]
categories = [
"algorithms",
"data-structures",
"no-std",
]
license = "MIT OR Apache-2.0"
repository = "https://github.com/tkaitchuck/ahash"
[package.metadata.docs.rs]
features = ["std"]
rustc-args = [
"-C",
"target-feature=+aes",
]
rustdoc-args = [
"-C",
"target-feature=+aes",
]
[profile.bench]
opt-level = 3
lto = "fat"
codegen-units = 1
debug = 0
debug-assertions = false
[profile.release]
opt-level = 3
lto = "fat"
codegen-units = 1
debug = 0
debug-assertions = false
[profile.test]
opt-level = 2
lto = "fat"
[lib]
name = "ahash"
path = "src/lib.rs"
test = true
doctest = true
bench = true
doc = true
[[test]]
name = "bench"
path = "tests/bench.rs"
[[test]]
name = "map_tests"
path = "tests/map_tests.rs"
[[test]]
name = "nopanic"
path = "tests/nopanic.rs"
[[bench]]
name = "ahash"
path = "tests/bench.rs"
harness = false
[[bench]]
name = "map"
path = "tests/map_tests.rs"
harness = false
[dependencies.cfg-if]
version = "1.0"
[dependencies.const-random]
version = "0.1.17"
optional = true
[dependencies.getrandom]
version = "0.3.1"
optional = true
[dependencies.portable-atomic]
version = "1.0.0"
optional = true
[dependencies.serde]
version = "1.0.117"
optional = true
[dependencies.zerocopy]
version = "0.8.24"
features = ["simd"]
default-features = false
[dev-dependencies.criterion]
version = "0.3.2"
features = ["html_reports"]
[dev-dependencies.fnv]
version = "1.0.5"
[dev-dependencies.fxhash]
version = "0.2.1"
[dev-dependencies.hashbrown]
version = "0.14.3"
[dev-dependencies.hex]
version = "0.4.2"
[dev-dependencies.no-panic]
version = "0.1.10"
[dev-dependencies.pcg-mwc]
version = "0.2.1"
[dev-dependencies.rand]
version = "0.8.5"
[dev-dependencies.seahash]
version = "4.0"
[dev-dependencies.serde_json]
version = "1.0.59"
[dev-dependencies.smallvec]
version = "1.13.1"
[build-dependencies.version_check]
version = "0.9.4"
[features]
atomic-polyfill = [
"dep:portable-atomic",
"once_cell/critical-section",
]
compile-time-rng = ["const-random"]
default = [
"std",
"runtime-rng",
]
nightly-arm-aes = []
no-rng = []
runtime-rng = ["getrandom"]
std = []
[target.'cfg(not(all(target_arch = "arm", target_os = "none")))'.dependencies.once_cell]
version = "1.18.0"
features = ["alloc"]
default-features = false
+118
View File
@@ -0,0 +1,118 @@
## How does aHash prevent DOS attacks
AHash is designed to [prevent an adversary that does not know the key from being able to create hash collisions or partial collisions.](https://github.com/tkaitchuck/aHash/wiki/How-aHash-is-resists-DOS-attacks)
If you are a cryptographer and would like to help review aHash's algorithm, please post a comment [here](https://github.com/tkaitchuck/aHash/issues/11).
In short, this is achieved by ensuring that:
* aHash is designed to [resist differential crypto analysis](https://github.com/tkaitchuck/aHash/wiki/How-aHash-is-resists-DOS-attacks#differential-analysis). Meaning it should not be possible to devise a scheme to "cancel" out a modification of the internal state from a block of input via some corresponding change in a subsequent block of input.
* This is achieved by not performing any "premixing" - This reversible mixing gave previous hashes such as murmurhash confidence in their quality, but could be undone by a deliberate attack.
* Before it is used each chunk of input is "masked" such as by xoring it with an unpredictable value.
* aHash obeys the '[strict avalanche criterion](https://en.wikipedia.org/wiki/Avalanche_effect#Strict_avalanche_criterion)':
Each bit of input has the potential to flip every bit of the output.
* Similarly, each bit in the key can affect every bit in the output.
* Input bits never effect just one, or a very few, bits in intermediate state. This is specifically designed to prevent the sort of
[differential attacks launched by the sipHash authors](https://emboss.github.io/blog/2012/12/14/breaking-murmur-hash-flooding-dos-reloaded/) which cancel previous inputs.
* The `finish` call at the end of the hash is designed to not expose individual bits of the internal state.
* For example in the main algorithm 256bits of state and 256bits of keys are reduced to 64 total bits using 3 rounds of AES encryption.
Reversing this is more than non-trivial. Most of the information is by definition gone, and any given bit of the internal state is fully diffused across the output.
* In both aHash and its fallback the internal state is divided into two halves which are updated by two unrelated techniques using the same input. - This means that if there is a way to attack one of them it likely won't be able to attack both of them at the same time.
* It is deliberately difficult to 'chain' collisions. (This has been the major technique used to weaponize attacks on other hash functions)
More details are available on [the wiki](https://github.com/tkaitchuck/aHash/wiki/How-aHash-is-resists-DOS-attacks).
## Why not use a cryptographic hash in a hashmap.
Cryptographic hashes are designed to make is nearly impossible to find two items that collide when the attacker has full control
over the input. This has several implications:
* They are very difficult to construct, and have to go to a lot of effort to ensure that collisions are not possible.
* They have no notion of a 'key'. Rather, they are fully deterministic and provide exactly one hash for a given input.
For a HashMap the requirements are different.
* Speed is very important, especially for short inputs. Often the key for a HashMap is a single `u32` or similar, and to be effective
the bucket that it should be hashed to needs to be computed in just a few CPU cycles.
* A hashmap does not need to provide a hard and fast guarantee that no two inputs will ever collide. Hence, hashCodes are not 256bits
but are just 64 or 32 bits in length. Often the first thing done with the hashcode is to truncate it further to compute which among a few buckets should be used for a key.
* Here collisions are expected, and a cheap to deal with provided there is no systematic way to generated huge numbers of values that all
go to the same bucket.
* This also means that unlike a cryptographic hash partial collisions matter. It doesn't do a hashmap any good to produce a unique 256bit hash if
the lower 12 bits are all the same. This means that even a provably irreversible hash would not offer protection from a DOS attack in a hashmap
because an attacker can easily just brute force the bottom N bits.
From a cryptography point of view, a hashmap needs something closer to a block cypher.
Where the input can be quickly mixed in a way that cannot be reversed without knowing a key.
## Why isn't aHash cryptographically secure
It is not designed to be.
Attempting to use aHash as a secure hash will likely fail to hold up for several reasons:
1. aHash relies on random keys which are assumed to not be observable by an attacker. For a cryptographic hash all inputs can be seen and controlled by the attacker.
2. aHash has not yet gone through peer review, which is a pre-requisite for security critical algorithms.
3. Because aHash uses reduced rounds of AES as opposed to the standard of 10. Things like the SQUARE attack apply to part of the internal state.
(These are mitigated by other means to prevent producing collections, but would be a problem in other contexts).
4. Like any cypher based hash, it will show certain statistical deviations from truly random output when comparing a (VERY) large number of hashes.
(By definition cyphers have fewer collisions than truly random data.)
There are efforts to build a secure hash function that uses AES-NI for acceleration, but aHash is not one of them.
## How is aHash so fast
AHash uses a number of tricks.
One trick is taking advantage of specialization. If aHash is compiled on nightly it will take
advantage of specialized hash implementations for strings, slices, and primitives.
Another is taking advantage of hardware instructions.
When it is available aHash uses AES rounds using the AES-NI instruction. AES-NI is very fast (on an intel i7-6700 it
is as fast as a 64 bit multiplication.) and handles 16 bytes of input at a time, while being a very strong permutation.
This is obviously much faster than most standard approaches to hashing, and does a better job of scrambling data than most non-secure hashes.
On an intel i7-6700 compiled on nightly Rust with flags `-C opt-level=3 -C target-cpu=native -C codegen-units=1`:
| Input | SipHash 1-3 time | FnvHash time|FxHash time| aHash time| aHash Fallback* |
|----------------|-----------|-----------|-----------|-----------|---------------|
| u8 | 9.3271 ns | 0.808 ns | **0.594 ns** | 0.7704 ns | 0.7664 ns |
| u16 | 9.5139 ns | 0.803 ns | **0.594 ns** | 0.7653 ns | 0.7704 ns |
| u32 | 9.1196 ns | 1.4424 ns | **0.594 ns** | 0.7637 ns | 0.7712 ns |
| u64 | 10.854 ns | 3.0484 ns | **0.628 ns** | 0.7788 ns | 0.7888 ns |
| u128 | 12.465 ns | 7.0728 ns | 0.799 ns | **0.6174 ns** | 0.6250 ns |
| 1 byte string | 11.745 ns | 2.4743 ns | 2.4000 ns | **1.4921 ns** | 1.5861 ns |
| 3 byte string | 12.066 ns | 3.5221 ns | 2.9253 ns | **1.4745 ns** | 1.8518 ns |
| 4 byte string | 11.634 ns | 4.0770 ns | 1.8818 ns | **1.5206 ns** | 1.8924 ns |
| 7 byte string | 14.762 ns | 5.9780 ns | 3.2282 ns | **1.5207 ns** | 1.8933 ns |
| 8 byte string | 13.442 ns | 4.0535 ns | 2.9422 ns | **1.6262 ns** | 1.8929 ns |
| 15 byte string | 16.880 ns | 8.3434 ns | 4.6070 ns | **1.6265 ns** | 1.7965 ns |
| 16 byte string | 15.155 ns | 7.5796 ns | 3.2619 ns | **1.6262 ns** | 1.8011 ns |
| 24 byte string | 16.521 ns | 12.492 ns | 3.5424 ns | **1.6266 ns** | 2.8311 ns |
| 68 byte string | 24.598 ns | 50.715 ns | 5.8312 ns | **4.8282 ns** | 5.4824 ns |
| 132 byte string| 39.224 ns | 119.96 ns | 11.777 ns | **6.5087 ns** | 9.1459 ns |
|1024 byte string| 254.00 ns | 1087.3 ns | 156.41 ns | **25.402 ns** | 54.566 ns |
* Fallback refers to the algorithm aHash would use if AES instructions are unavailable.
For reference a hash that does nothing (not even reads the input data takes) **0.520 ns**. So that represents the fastest
possible time.
As you can see above aHash like `FxHash` provides a large speedup over `SipHash-1-3` which is already nearly twice as fast as `SipHash-2-4`.
Rust's HashMap by default uses `SipHash-1-3` because faster hash functions such as `FxHash` are predictable and vulnerable to denial of
service attacks. While `aHash` has both very strong scrambling and very high performance.
AHash performs well when dealing with large inputs because aHash reads 8 or 16 bytes at a time. (depending on availability of AES-NI)
Because of this, and its optimized logic, `aHash` is able to outperform `FxHash` with strings.
It also provides especially good performance dealing with unaligned input.
(Notice the big performance gaps between 3 vs 4, 7 vs 8 and 15 vs 16 in `FxHash` above)
### Which CPUs can use the hardware acceleration
Hardware AES instructions are built into Intel processors built after 2010 and AMD processors after 2012.
It is also available on [many other CPUs](https://en.wikipedia.org/wiki/AES_instruction_set) should in eventually
be able to get aHash to work. However, only X86 and X86-64 are the only supported architectures at the moment, as currently
they are the only architectures for which Rust provides an intrinsic.
aHash also uses `sse2` and `sse3` instructions. X86 processors that have `aesni` also have these instruction sets.
+201
View File
@@ -0,0 +1,201 @@
Apache License
Version 2.0, January 2004
http://www.apache.org/licenses/
TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION
1. Definitions.
"License" shall mean the terms and conditions for use, reproduction,
and distribution as defined by Sections 1 through 9 of this document.
"Licensor" shall mean the copyright owner or entity authorized by
the copyright owner that is granting the License.
"Legal Entity" shall mean the union of the acting entity and all
other entities that control, are controlled by, or are under common
control with that entity. For the purposes of this definition,
"control" means (i) the power, direct or indirect, to cause the
direction or management of such entity, whether by contract or
otherwise, or (ii) ownership of fifty percent (50%) or more of the
outstanding shares, or (iii) beneficial ownership of such entity.
"You" (or "Your") shall mean an individual or Legal Entity
exercising permissions granted by this License.
"Source" form shall mean the preferred form for making modifications,
including but not limited to software source code, documentation
source, and configuration files.
"Object" form shall mean any form resulting from mechanical
transformation or translation of a Source form, including but
not limited to compiled object code, generated documentation,
and conversions to other media types.
"Work" shall mean the work of authorship, whether in Source or
Object form, made available under the License, as indicated by a
copyright notice that is included in or attached to the work
(an example is provided in the Appendix below).
"Derivative Works" shall mean any work, whether in Source or Object
form, that is based on (or derived from) the Work and for which the
editorial revisions, annotations, elaborations, or other modifications
represent, as a whole, an original work of authorship. For the purposes
of this License, Derivative Works shall not include works that remain
separable from, or merely link (or bind by name) to the interfaces of,
the Work and Derivative Works thereof.
"Contribution" shall mean any work of authorship, including
the original version of the Work and any modifications or additions
to that Work or Derivative Works thereof, that is intentionally
submitted to Licensor for inclusion in the Work by the copyright owner
or by an individual or Legal Entity authorized to submit on behalf of
the copyright owner. For the purposes of this definition, "submitted"
means any form of electronic, verbal, or written communication sent
to the Licensor or its representatives, including but not limited to
communication on electronic mailing lists, source code control systems,
and issue tracking systems that are managed by, or on behalf of, the
Licensor for the purpose of discussing and improving the Work, but
excluding communication that is conspicuously marked or otherwise
designated in writing by the copyright owner as "Not a Contribution."
"Contributor" shall mean Licensor and any individual or Legal Entity
on behalf of whom a Contribution has been received by Licensor and
subsequently incorporated within the Work.
2. Grant of Copyright License. Subject to the terms and conditions of
this License, each Contributor hereby grants to You a perpetual,
worldwide, non-exclusive, no-charge, royalty-free, irrevocable
copyright license to reproduce, prepare Derivative Works of,
publicly display, publicly perform, sublicense, and distribute the
Work and such Derivative Works in Source or Object form.
3. Grant of Patent License. Subject to the terms and conditions of
this License, each Contributor hereby grants to You a perpetual,
worldwide, non-exclusive, no-charge, royalty-free, irrevocable
(except as stated in this section) patent license to make, have made,
use, offer to sell, sell, import, and otherwise transfer the Work,
where such license applies only to those patent claims licensable
by such Contributor that are necessarily infringed by their
Contribution(s) alone or by combination of their Contribution(s)
with the Work to which such Contribution(s) was submitted. If You
institute patent litigation against any entity (including a
cross-claim or counterclaim in a lawsuit) alleging that the Work
or a Contribution incorporated within the Work constitutes direct
or contributory patent infringement, then any patent licenses
granted to You under this License for that Work shall terminate
as of the date such litigation is filed.
4. Redistribution. You may reproduce and distribute copies of the
Work or Derivative Works thereof in any medium, with or without
modifications, and in Source or Object form, provided that You
meet the following conditions:
(a) You must give any other recipients of the Work or
Derivative Works a copy of this License; and
(b) You must cause any modified files to carry prominent notices
stating that You changed the files; and
(c) You must retain, in the Source form of any Derivative Works
that You distribute, all copyright, patent, trademark, and
attribution notices from the Source form of the Work,
excluding those notices that do not pertain to any part of
the Derivative Works; and
(d) If the Work includes a "NOTICE" text file as part of its
distribution, then any Derivative Works that You distribute must
include a readable copy of the attribution notices contained
within such NOTICE file, excluding those notices that do not
pertain to any part of the Derivative Works, in at least one
of the following places: within a NOTICE text file distributed
as part of the Derivative Works; within the Source form or
documentation, if provided along with the Derivative Works; or,
within a display generated by the Derivative Works, if and
wherever such third-party notices normally appear. The contents
of the NOTICE file are for informational purposes only and
do not modify the License. You may add Your own attribution
notices within Derivative Works that You distribute, alongside
or as an addendum to the NOTICE text from the Work, provided
that such additional attribution notices cannot be construed
as modifying the License.
You may add Your own copyright statement to Your modifications and
may provide additional or different license terms and conditions
for use, reproduction, or distribution of Your modifications, or
for any such Derivative Works as a whole, provided Your use,
reproduction, and distribution of the Work otherwise complies with
the conditions stated in this License.
5. Submission of Contributions. Unless You explicitly state otherwise,
any Contribution intentionally submitted for inclusion in the Work
by You to the Licensor shall be under the terms and conditions of
this License, without any additional terms or conditions.
Notwithstanding the above, nothing herein shall supersede or modify
the terms of any separate license agreement you may have executed
with Licensor regarding such Contributions.
6. Trademarks. This License does not grant permission to use the trade
names, trademarks, service marks, or product names of the Licensor,
except as required for reasonable and customary use in describing the
origin of the Work and reproducing the content of the NOTICE file.
7. Disclaimer of Warranty. Unless required by applicable law or
agreed to in writing, Licensor provides the Work (and each
Contributor provides its Contributions) on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or
implied, including, without limitation, any warranties or conditions
of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A
PARTICULAR PURPOSE. You are solely responsible for determining the
appropriateness of using or redistributing the Work and assume any
risks associated with Your exercise of permissions under this License.
8. Limitation of Liability. In no event and under no legal theory,
whether in tort (including negligence), contract, or otherwise,
unless required by applicable law (such as deliberate and grossly
negligent acts) or agreed to in writing, shall any Contributor be
liable to You for damages, including any direct, indirect, special,
incidental, or consequential damages of any character arising as a
result of this License or out of the use or inability to use the
Work (including but not limited to damages for loss of goodwill,
work stoppage, computer failure or malfunction, or any and all
other commercial damages or losses), even if such Contributor
has been advised of the possibility of such damages.
9. Accepting Warranty or Additional Liability. While redistributing
the Work or Derivative Works thereof, You may choose to offer,
and charge a fee for, acceptance of support, warranty, indemnity,
or other liability obligations and/or rights consistent with this
License. However, in accepting such obligations, You may act only
on Your own behalf and on Your sole responsibility, not on behalf
of any other Contributor, and only if You agree to indemnify,
defend, and hold each Contributor harmless for any liability
incurred by, or claims asserted against, such Contributor by reason
of your accepting any such warranty or additional liability.
END OF TERMS AND CONDITIONS
APPENDIX: How to apply the Apache License to your work.
To apply the Apache License to your work, attach the following
boilerplate notice, with the fields enclosed by brackets "[]"
replaced with your own identifying information. (Don't include
the brackets!) The text should be enclosed in the appropriate
comment syntax for the file format. We also recommend that a
file or class name and description of purpose be included on the
same "printed page" as the copyright notice for easier
identification within third-party archives.
Copyright [yyyy] [name of copyright owner]
Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.
You may obtain a copy of the License at
http://www.apache.org/licenses/LICENSE-2.0
Unless required by applicable law or agreed to in writing, software
distributed under the License is distributed on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the License for the specific language governing permissions and
limitations under the License.
+25
View File
@@ -0,0 +1,25 @@
Copyright (c) 2018 Tom Kaitchuck
Permission is hereby granted, free of charge, to any
person obtaining a copy of this software and associated
documentation files (the "Software"), to deal in the
Software without restriction, including without
limitation the rights to use, copy, modify, merge,
publish, distribute, sublicense, and/or sell copies of
the Software, and to permit persons to whom the Software
is furnished to do so, subject to the following
conditions:
The above copyright notice and this permission notice
shall be included in all copies or substantial portions
of the Software.
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF
ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED
TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A
PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT
SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY
CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR
IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER
DEALINGS IN THE SOFTWARE.
+109
View File
@@ -0,0 +1,109 @@
# aHash ![Build Status](https://img.shields.io/github/actions/workflow/status/tkaitchuck/aHash/rust.yml?branch=master) ![Licence](https://img.shields.io/crates/l/ahash) ![Downloads](https://img.shields.io/crates/d/ahash)
AHash is the [fastest](https://github.com/tkaitchuck/aHash/blob/master/compare/readme.md#Speed),
[DOS resistant hash](https://github.com/tkaitchuck/aHash/wiki/How-aHash-is-resists-DOS-attacks) currently available in Rust.
AHash is intended *exclusively* for use in in-memory hashmaps.
AHash's output is of [high quality](https://github.com/tkaitchuck/aHash/blob/master/compare/readme.md#Quality) but aHash is **not** a cryptographically secure hash.
## Design
Because AHash is a keyed hash, each map will produce completely different hashes, which cannot be predicted without knowing the keys.
[This prevents DOS attacks where an attacker sends a large number of items whose hashes collide that get used as keys in a hashmap.](https://github.com/tkaitchuck/aHash/wiki/How-aHash-is-resists-DOS-attacks)
This also avoids [accidentally quadratic behavior by reading from one map and writing to another.](https://accidentallyquadratic.tumblr.com/post/153545455987/rust-hash-iteration-reinsertion)
## Goals and Non-Goals
AHash does *not* have a fixed standard for its output. This allows it to improve over time. For example,
if any faster algorithm is found, aHash will be updated to incorporate the technique.
Similarly, should any flaw in aHash's DOS resistance be found, aHash will be changed to correct the flaw.
Because it does not have a fixed standard, different computers or computers on different versions of the code will observe different hash values.
As such, aHash is not recommended for use other than in-memory maps. Specifically, aHash is not intended for network use or in applications which persist hashed values.
(In these cases `HighwayHash` would be a better choice)
Additionally, aHash is not intended to be cryptographically secure and should not be used as a MAC, or anywhere which requires a cryptographically secure hash.
(In these cases `SHA-3` would be a better choice)
## Usage
AHash is a drop in replacement for the default implementation of the `Hasher` trait. To construct a `HashMap` using aHash
as its hasher do the following:
```rust
use ahash::{AHasher, RandomState};
use std::collections::HashMap;
let mut map: HashMap<i32, i32, RandomState> = HashMap::default();
map.insert(12, 34);
```
For convenience, wrappers called `AHashMap` and `AHashSet` are also provided.
These do the same thing with slightly less typing.
```rust
use ahash::AHashMap;
let mut map: AHashMap<i32, i32> = AHashMap::new();
map.insert(12, 34);
map.insert(56, 78);
```
## Flags
The aHash package has the following flags:
* `std`: This enables features which require the standard library. (On by default) This includes providing the utility classes `AHashMap` and `AHashSet`.
* `serde`: Enables `serde` support for the utility classes `AHashMap` and `AHashSet`.
* `runtime-rng`: To obtain a seed for Hashers will obtain randomness from the operating system. (On by default)
This is done using the [getrandom](https://github.com/rust-random/getrandom) crate.
* `compile-time-rng`: For OS targets without access to a random number generator, `compile-time-rng` provides an alternative.
If `getrandom` is unavailable and `compile-time-rng` is enabled, aHash will generate random numbers at compile time and embed them in the binary.
* `nightly-arm-aes`: To use AES instructions on 32-bit ARM, which requires nightly. This is not needed on AArch64.
This allows for DOS resistance even if there is no random number generator available at runtime (assuming the compiled binary is not public).
This makes the binary non-deterministic. (If non-determinism is a problem see [constrandom's documentation](https://github.com/tkaitchuck/constrandom#deterministic-builds))
If both `runtime-rng` and `compile-time-rng` are enabled the `runtime-rng` will take precedence and `compile-time-rng` will do nothing.
If neither flag is set, seeds can be supplied by the application. [Multiple apis](https://docs.rs/ahash/latest/ahash/random_state/struct.RandomState.html)
are available to do this.
## Comparison with other hashers
A full comparison with other hashing algorithms can be found [here](https://github.com/tkaitchuck/aHash/blob/master/compare/readme.md)
![Hasher performance](https://docs.google.com/spreadsheets/d/e/2PACX-1vSK7Li2nS-Bur9arAYF9IfT37MP-ohAe1v19lZu5fd9MajI1fSveLAQZyEie4Ea9k5-SWHTff7nL2DW/pubchart?oid=1323618938&format=image)
For a more representative performance comparison which includes the overhead of using a HashMap, see [HashBrown's benchmarks](https://github.com/rust-lang/hashbrown#performance)
as HashBrown now uses aHash as its hasher by default.
## Hash quality
AHash passes the full [SMHasher test suite](https://github.com/rurban/smhasher).
The code to reproduce the result, and the full output [are checked into the repo](https://github.com/tkaitchuck/aHash/tree/master/smhasher).
## Additional FAQ
A separate FAQ document is maintained [here](https://github.com/tkaitchuck/aHash/blob/master/FAQ.md).
If you have questions not covered there, open an issue [here](https://github.com/tkaitchuck/aHash/issues).
## License
Licensed under either of:
* Apache License, Version 2.0, ([LICENSE-APACHE](LICENSE-APACHE) or http://www.apache.org/licenses/LICENSE-2.0)
* MIT license ([LICENSE-MIT](LICENSE-MIT) or http://opensource.org/licenses/MIT)
at your option.
## Contribution
Unless you explicitly state otherwise, any contribution intentionally submitted
for inclusion in the work by you, as defined in the Apache-2.0 license, shall be dual licensed as above, without any
additional terms or conditions.
+22
View File
@@ -0,0 +1,22 @@
#![deny(warnings)]
use std::env;
fn main() {
println!("cargo:rerun-if-changed=build.rs");
println!("cargo:rustc-check-cfg=cfg(specialize)");
if let Some(true) = version_check::supports_feature("specialize") {
println!("cargo:rustc-cfg=specialize");
}
let arch = env::var("CARGO_CFG_TARGET_ARCH").expect("CARGO_CFG_TARGET_ARCH was not set");
println!("cargo:rustc-check-cfg=cfg(folded_multiply)");
if arch.eq_ignore_ascii_case("x86_64")
|| arch.eq_ignore_ascii_case("aarch64")
|| arch.eq_ignore_ascii_case("mips64")
|| arch.eq_ignore_ascii_case("powerpc64")
|| arch.eq_ignore_ascii_case("riscv64gc")
|| arch.eq_ignore_ascii_case("s390x")
{
println!("cargo:rustc-cfg=folded_multiply");
}
}
+1
View File
@@ -0,0 +1 @@
max_width = 120
+426
View File
@@ -0,0 +1,426 @@
use crate::convert::*;
use crate::operations::*;
use crate::random_state::PI;
use crate::RandomState;
use core::hash::Hasher;
/// A `Hasher` for hashing an arbitrary stream of bytes.
///
/// Instances of [`AHasher`] represent state that is updated while hashing data.
///
/// Each method updates the internal state based on the new data provided. Once
/// all of the data has been provided, the resulting hash can be obtained by calling
/// `finish()`
///
/// [Clone] is also provided in case you wish to calculate hashes for two different items that
/// start with the same data.
///
#[derive(Debug, Clone)]
pub struct AHasher {
enc: u128,
sum: u128,
key: u128,
}
impl AHasher {
/// Creates a new hasher keyed to the provided keys.
///
/// Normally hashers are created via `AHasher::default()` for fixed keys or `RandomState::new()` for randomly
/// generated keys and `RandomState::with_seeds(a,b)` for seeds that are set and can be reused. All of these work at
/// map creation time (and hence don't have any overhead on a per-item bais).
///
/// This method directly creates the hasher instance and performs no transformation on the provided seeds. This may
/// be useful where a HashBuilder is not desired, such as for testing purposes.
///
/// # Example
///
/// ```no_build
/// use std::hash::Hasher;
/// use ahash::AHasher;
///
/// let mut hasher = AHasher::new_with_keys(1234, 5678);
///
/// hasher.write_u32(1989);
/// hasher.write_u8(11);
/// hasher.write_u8(9);
/// hasher.write(b"Huh?");
///
/// println!("Hash is {:x}!", hasher.finish());
/// ```
#[inline]
pub(crate) fn new_with_keys(key1: u128, key2: u128) -> Self {
let pi: [u128; 2] = PI.convert();
let key1 = key1 ^ pi[0];
let key2 = key2 ^ pi[1];
Self {
enc: key1,
sum: key2,
key: key1 ^ key2,
}
}
#[allow(unused)] // False positive
pub(crate) fn test_with_keys(key1: u128, key2: u128) -> Self {
Self {
enc: key1,
sum: key2,
key: key1 ^ key2,
}
}
#[inline]
pub(crate) fn from_random_state(rand_state: &RandomState) -> Self {
let key1 = [rand_state.k0, rand_state.k1].convert();
let key2 = [rand_state.k2, rand_state.k3].convert();
Self {
enc: key1,
sum: key2,
key: key1 ^ key2,
}
}
#[inline(always)]
fn hash_in(&mut self, new_value: u128) {
self.enc = aesdec(self.enc, new_value);
self.sum = shuffle_and_add(self.sum, new_value);
}
#[inline(always)]
fn hash_in_2(&mut self, v1: u128, v2: u128) {
self.enc = aesdec(self.enc, v1);
self.sum = shuffle_and_add(self.sum, v1);
self.enc = aesdec(self.enc, v2);
self.sum = shuffle_and_add(self.sum, v2);
}
#[inline]
#[cfg(specialize)]
fn short_finish(&self) -> u64 {
let combined = aesenc(self.sum, self.enc);
let result: [u64; 2] = aesdec(combined, combined).convert();
result[0]
}
}
/// Provides [Hasher] methods to hash all of the primitive types.
///
/// [Hasher]: core::hash::Hasher
impl Hasher for AHasher {
#[inline]
fn write_u8(&mut self, i: u8) {
self.write_u64(i as u64);
}
#[inline]
fn write_u16(&mut self, i: u16) {
self.write_u64(i as u64);
}
#[inline]
fn write_u32(&mut self, i: u32) {
self.write_u64(i as u64);
}
#[inline]
fn write_u128(&mut self, i: u128) {
self.hash_in(i);
}
#[inline]
#[cfg(any(
target_pointer_width = "64",
target_pointer_width = "32",
target_pointer_width = "16"
))]
fn write_usize(&mut self, i: usize) {
self.write_u64(i as u64);
}
#[inline]
#[cfg(target_pointer_width = "128")]
fn write_usize(&mut self, i: usize) {
self.write_u128(i as u128);
}
#[inline]
fn write_u64(&mut self, i: u64) {
self.write_u128(i as u128);
}
#[inline]
#[allow(clippy::collapsible_if)]
fn write(&mut self, input: &[u8]) {
let mut data = input;
let length = data.len();
add_in_length(&mut self.enc, length as u64);
//A 'binary search' on sizes reduces the number of comparisons.
if data.len() <= 8 {
let value = read_small(data);
self.hash_in(value.convert());
} else {
if data.len() > 32 {
if data.len() > 64 {
let tail = data.read_last_u128x4();
let mut current: [u128; 4] = [self.key; 4];
current[0] = aesenc(current[0], tail[0]);
current[1] = aesdec(current[1], tail[1]);
current[2] = aesenc(current[2], tail[2]);
current[3] = aesdec(current[3], tail[3]);
let mut sum: [u128; 2] = [self.key, !self.key];
sum[0] = add_by_64s(sum[0].convert(), tail[0].convert()).convert();
sum[1] = add_by_64s(sum[1].convert(), tail[1].convert()).convert();
sum[0] = shuffle_and_add(sum[0], tail[2]);
sum[1] = shuffle_and_add(sum[1], tail[3]);
while data.len() > 64 {
let (blocks, rest) = data.read_u128x4();
current[0] = aesdec(current[0], blocks[0]);
current[1] = aesdec(current[1], blocks[1]);
current[2] = aesdec(current[2], blocks[2]);
current[3] = aesdec(current[3], blocks[3]);
sum[0] = shuffle_and_add(sum[0], blocks[0]);
sum[1] = shuffle_and_add(sum[1], blocks[1]);
sum[0] = shuffle_and_add(sum[0], blocks[2]);
sum[1] = shuffle_and_add(sum[1], blocks[3]);
data = rest;
}
self.hash_in_2(current[0], current[1]);
self.hash_in_2(current[2], current[3]);
self.hash_in_2(sum[0], sum[1]);
} else {
//len 33-64
let (head, _) = data.read_u128x2();
let tail = data.read_last_u128x2();
self.hash_in_2(head[0], head[1]);
self.hash_in_2(tail[0], tail[1]);
}
} else {
if data.len() > 16 {
//len 17-32
self.hash_in_2(data.read_u128().0, data.read_last_u128());
} else {
//len 9-16
let value: [u64; 2] = [data.read_u64().0, data.read_last_u64()];
self.hash_in(value.convert());
}
}
}
}
#[inline]
fn finish(&self) -> u64 {
let combined = aesenc(self.sum, self.enc);
let result: [u64; 2] = aesdec(aesdec(combined, self.key), combined).convert();
result[0]
}
}
#[cfg(specialize)]
pub(crate) struct AHasherU64 {
pub(crate) buffer: u64,
pub(crate) pad: u64,
}
/// A specialized hasher for only primitives under 64 bits.
#[cfg(specialize)]
impl Hasher for AHasherU64 {
#[inline]
fn finish(&self) -> u64 {
folded_multiply(self.buffer, self.pad)
}
#[inline]
fn write(&mut self, _bytes: &[u8]) {
unreachable!("Specialized hasher was called with a different type of object")
}
#[inline]
fn write_u8(&mut self, i: u8) {
self.write_u64(i as u64);
}
#[inline]
fn write_u16(&mut self, i: u16) {
self.write_u64(i as u64);
}
#[inline]
fn write_u32(&mut self, i: u32) {
self.write_u64(i as u64);
}
#[inline]
fn write_u64(&mut self, i: u64) {
self.buffer = folded_multiply(i ^ self.buffer, MULTIPLE);
}
#[inline]
fn write_u128(&mut self, _i: u128) {
unreachable!("Specialized hasher was called with a different type of object")
}
#[inline]
fn write_usize(&mut self, _i: usize) {
unreachable!("Specialized hasher was called with a different type of object")
}
}
#[cfg(specialize)]
pub(crate) struct AHasherFixed(pub AHasher);
/// A specialized hasher for fixed size primitives larger than 64 bits.
#[cfg(specialize)]
impl Hasher for AHasherFixed {
#[inline]
fn finish(&self) -> u64 {
self.0.short_finish()
}
#[inline]
fn write(&mut self, bytes: &[u8]) {
self.0.write(bytes)
}
#[inline]
fn write_u8(&mut self, i: u8) {
self.write_u64(i as u64);
}
#[inline]
fn write_u16(&mut self, i: u16) {
self.write_u64(i as u64);
}
#[inline]
fn write_u32(&mut self, i: u32) {
self.write_u64(i as u64);
}
#[inline]
fn write_u64(&mut self, i: u64) {
self.0.write_u64(i);
}
#[inline]
fn write_u128(&mut self, i: u128) {
self.0.write_u128(i);
}
#[inline]
fn write_usize(&mut self, i: usize) {
self.0.write_usize(i);
}
}
#[cfg(specialize)]
pub(crate) struct AHasherStr(pub AHasher);
/// A specialized hasher for strings
/// Note that the other types don't panic because the hash impl for String tacks on an unneeded call. (As does vec)
#[cfg(specialize)]
impl Hasher for AHasherStr {
#[inline]
fn finish(&self) -> u64 {
let result: [u64; 2] = self.0.enc.convert();
result[0]
}
#[inline]
fn write(&mut self, bytes: &[u8]) {
if bytes.len() > 8 {
self.0.write(bytes);
self.0.enc = aesenc(self.0.sum, self.0.enc);
self.0.enc = aesdec(aesdec(self.0.enc, self.0.key), self.0.enc);
} else {
add_in_length(&mut self.0.enc, bytes.len() as u64);
let value = read_small(bytes).convert();
self.0.sum = shuffle_and_add(self.0.sum, value);
self.0.enc = aesenc(self.0.sum, self.0.enc);
self.0.enc = aesdec(aesdec(self.0.enc, self.0.key), self.0.enc);
}
}
#[inline]
fn write_u8(&mut self, _i: u8) {}
#[inline]
fn write_u16(&mut self, _i: u16) {}
#[inline]
fn write_u32(&mut self, _i: u32) {}
#[inline]
fn write_u64(&mut self, _i: u64) {}
#[inline]
fn write_u128(&mut self, _i: u128) {}
#[inline]
fn write_usize(&mut self, _i: usize) {}
}
#[cfg(test)]
mod tests {
use super::*;
use crate::convert::Convert;
use crate::operations::aesenc;
use crate::RandomState;
use std::hash::{BuildHasher, Hasher};
#[test]
fn test_sanity() {
let mut hasher = RandomState::with_seeds(1, 2, 3, 4).build_hasher();
hasher.write_u64(0);
let h1 = hasher.finish();
hasher.write(&[1, 0, 0, 0, 0, 0, 0, 0]);
let h2 = hasher.finish();
assert_ne!(h1, h2);
}
#[cfg(feature = "compile-time-rng")]
#[test]
fn test_builder() {
use std::collections::HashMap;
use std::hash::BuildHasherDefault;
let mut map = HashMap::<u32, u64, BuildHasherDefault<AHasher>>::default();
map.insert(1, 3);
}
#[cfg(feature = "compile-time-rng")]
#[test]
fn test_default() {
let hasher_a = AHasher::default();
let a_enc: [u64; 2] = hasher_a.enc.convert();
let a_sum: [u64; 2] = hasher_a.sum.convert();
assert_ne!(0, a_enc[0]);
assert_ne!(0, a_enc[1]);
assert_ne!(0, a_sum[0]);
assert_ne!(0, a_sum[1]);
assert_ne!(a_enc[0], a_enc[1]);
assert_ne!(a_sum[0], a_sum[1]);
assert_ne!(a_enc[0], a_sum[0]);
assert_ne!(a_enc[1], a_sum[1]);
let hasher_b = AHasher::default();
let b_enc: [u64; 2] = hasher_b.enc.convert();
let b_sum: [u64; 2] = hasher_b.sum.convert();
assert_eq!(a_enc[0], b_enc[0]);
assert_eq!(a_enc[1], b_enc[1]);
assert_eq!(a_sum[0], b_sum[0]);
assert_eq!(a_sum[1], b_sum[1]);
}
#[test]
fn test_hash() {
let mut result: [u64; 2] = [0x6c62272e07bb0142, 0x62b821756295c58d];
let value: [u64; 2] = [1 << 32, 0xFEDCBA9876543210];
result = aesenc(value.convert(), result.convert()).convert();
result = aesenc(result.convert(), result.convert()).convert();
let mut result2: [u64; 2] = [0x6c62272e07bb0142, 0x62b821756295c58d];
let value2: [u64; 2] = [1, 0xFEDCBA9876543210];
result2 = aesenc(value2.convert(), result2.convert()).convert();
result2 = aesenc(result2.convert(), result.convert()).convert();
let result: [u8; 16] = result.convert();
let result2: [u8; 16] = result2.convert();
assert_ne!(hex::encode(result), hex::encode(result2));
}
}
+151
View File
@@ -0,0 +1,151 @@
pub(crate) trait Convert<To> {
fn convert(self) -> To;
}
macro_rules! convert {
($a:ty, $b:ty) => {
impl Convert<$b> for $a {
#[inline(always)]
fn convert(self) -> $b {
zerocopy::transmute!(self)
}
}
impl Convert<$a> for $b {
#[inline(always)]
fn convert(self) -> $a {
zerocopy::transmute!(self)
}
}
};
}
macro_rules! convert_primitive_bytes {
($a:ty, $b:ty) => {
impl Convert<$b> for $a {
#[inline(always)]
fn convert(self) -> $b {
self.to_ne_bytes()
}
}
impl Convert<$a> for $b {
#[inline(always)]
fn convert(self) -> $a {
<$a>::from_ne_bytes(self)
}
}
};
}
convert!([u128; 4], [u8; 64]);
convert!([u128; 2], [u64; 4]);
convert!([u128; 2], [u8; 32]);
convert!(u128, [u64; 2]);
convert_primitive_bytes!(u128, [u8; 16]);
convert!([u64; 2], [u32; 4]);
#[cfg(test)]
convert!([u64; 2], [u8; 16]);
convert_primitive_bytes!(u64, [u8; 8]);
convert_primitive_bytes!(u32, [u8; 4]);
convert_primitive_bytes!(u16, [u8; 2]);
convert!([[u64; 4]; 2], [u8; 64]);
macro_rules! as_array {
($input:expr, $len:expr) => {{
{
#[inline(always)]
fn as_array<T>(slice: &[T]) -> &[T; $len] {
core::convert::TryFrom::try_from(slice).unwrap()
}
as_array($input)
}
}};
}
pub(crate) trait ReadFromSlice {
fn read_u16(&self) -> (u16, &[u8]);
fn read_u32(&self) -> (u32, &[u8]);
fn read_u64(&self) -> (u64, &[u8]);
fn read_u128(&self) -> (u128, &[u8]);
fn read_u128x2(&self) -> ([u128; 2], &[u8]);
fn read_u128x4(&self) -> ([u128; 4], &[u8]);
fn read_last_u16(&self) -> u16;
fn read_last_u32(&self) -> u32;
fn read_last_u64(&self) -> u64;
fn read_last_u128(&self) -> u128;
fn read_last_u128x2(&self) -> [u128; 2];
fn read_last_u128x4(&self) -> [u128; 4];
}
impl ReadFromSlice for [u8] {
#[inline(always)]
fn read_u16(&self) -> (u16, &[u8]) {
let (value, rest) = self.split_at(2);
(as_array!(value, 2).convert(), rest)
}
#[inline(always)]
fn read_u32(&self) -> (u32, &[u8]) {
let (value, rest) = self.split_at(4);
(as_array!(value, 4).convert(), rest)
}
#[inline(always)]
fn read_u64(&self) -> (u64, &[u8]) {
let (value, rest) = self.split_at(8);
(as_array!(value, 8).convert(), rest)
}
#[inline(always)]
fn read_u128(&self) -> (u128, &[u8]) {
let (value, rest) = self.split_at(16);
(as_array!(value, 16).convert(), rest)
}
#[inline(always)]
fn read_u128x2(&self) -> ([u128; 2], &[u8]) {
let (value, rest) = self.split_at(32);
(as_array!(value, 32).convert(), rest)
}
#[inline(always)]
fn read_u128x4(&self) -> ([u128; 4], &[u8]) {
let (value, rest) = self.split_at(64);
(as_array!(value, 64).convert(), rest)
}
#[inline(always)]
fn read_last_u16(&self) -> u16 {
let (_, value) = self.split_at(self.len() - 2);
as_array!(value, 2).convert()
}
#[inline(always)]
fn read_last_u32(&self) -> u32 {
let (_, value) = self.split_at(self.len() - 4);
as_array!(value, 4).convert()
}
#[inline(always)]
fn read_last_u64(&self) -> u64 {
let (_, value) = self.split_at(self.len() - 8);
as_array!(value, 8).convert()
}
#[inline(always)]
fn read_last_u128(&self) -> u128 {
let (_, value) = self.split_at(self.len() - 16);
as_array!(value, 16).convert()
}
#[inline(always)]
fn read_last_u128x2(&self) -> [u128; 2] {
let (_, value) = self.split_at(self.len() - 32);
as_array!(value, 32).convert()
}
#[inline(always)]
fn read_last_u128x4(&self) -> [u128; 4] {
let (_, value) = self.split_at(self.len() - 64);
as_array!(value, 64).convert()
}
}
+360
View File
@@ -0,0 +1,360 @@
use crate::convert::*;
use crate::operations::folded_multiply;
use crate::operations::read_small;
use crate::operations::MULTIPLE;
use crate::random_state::PI;
use crate::RandomState;
use core::hash::Hasher;
const ROT: u32 = 23; //17
/// A `Hasher` for hashing an arbitrary stream of bytes.
///
/// Instances of [`AHasher`] represent state that is updated while hashing data.
///
/// Each method updates the internal state based on the new data provided. Once
/// all of the data has been provided, the resulting hash can be obtained by calling
/// `finish()`
///
/// [Clone] is also provided in case you wish to calculate hashes for two different items that
/// start with the same data.
///
#[derive(Debug, Clone)]
pub struct AHasher {
buffer: u64,
pad: u64,
extra_keys: [u64; 2],
}
impl AHasher {
/// Creates a new hasher keyed to the provided key.
#[inline]
#[allow(dead_code)] // Is not called if non-fallback hash is used.
pub(crate) fn new_with_keys(key1: u128, key2: u128) -> AHasher {
let pi: [u128; 2] = PI.convert();
let key1: [u64; 2] = (key1 ^ pi[0]).convert();
let key2: [u64; 2] = (key2 ^ pi[1]).convert();
AHasher {
buffer: key1[0],
pad: key1[1],
extra_keys: key2,
}
}
#[allow(unused)] // False positive
pub(crate) fn test_with_keys(key1: u128, key2: u128) -> Self {
let key1: [u64; 2] = key1.convert();
let key2: [u64; 2] = key2.convert();
Self {
buffer: key1[0],
pad: key1[1],
extra_keys: key2,
}
}
#[inline]
#[allow(dead_code)] // Is not called if non-fallback hash is used.
pub(crate) fn from_random_state(rand_state: &RandomState) -> AHasher {
AHasher {
buffer: rand_state.k1,
pad: rand_state.k0,
extra_keys: [rand_state.k2, rand_state.k3],
}
}
/// This update function has the goal of updating the buffer with a single multiply
/// FxHash does this but is vulnerable to attack. To avoid this input needs to be masked to with an
/// unpredictable value. Other hashes such as murmurhash have taken this approach but were found vulnerable
/// to attack. The attack was based on the idea of reversing the pre-mixing (Which is necessarily
/// reversible otherwise bits would be lost) then placing a difference in the highest bit before the
/// multiply used to mix the data. Because a multiply can never affect the bits to the right of it, a
/// subsequent update that also differed in this bit could result in a predictable collision.
///
/// This version avoids this vulnerability while still only using a single multiply. It takes advantage
/// of the fact that when a 64 bit multiply is performed the upper 64 bits are usually computed and thrown
/// away. Instead it creates two 128 bit values where the upper 64 bits are zeros and multiplies them.
/// (The compiler is smart enough to turn this into a 64 bit multiplication in the assembly)
/// Then the upper bits are xored with the lower bits to produce a single 64 bit result.
///
/// To understand why this is a good scrambling function it helps to understand multiply-with-carry PRNGs:
/// https://en.wikipedia.org/wiki/Multiply-with-carry_pseudorandom_number_generator
/// If the multiple is chosen well, this creates a long period, decent quality PRNG.
/// Notice that this function is equivalent to this except the `buffer`/`state` is being xored with each
/// new block of data. In the event that data is all zeros, it is exactly equivalent to a MWC PRNG.
///
/// This is impervious to attack because every bit buffer at the end is dependent on every bit in
/// `new_data ^ buffer`. For example suppose two inputs differed in only the 5th bit. Then when the
/// multiplication is performed the `result` will differ in bits 5-69. More specifically it will differ by
/// 2^5 * MULTIPLE. However in the next step bits 65-128 are turned into a separate 64 bit value. So the
/// differing bits will be in the lower 6 bits of this value. The two intermediate values that differ in
/// bits 5-63 and in bits 0-5 respectively get added together. Producing an output that differs in every
/// bit. The addition carries in the multiplication and at the end additionally mean that the even if an
/// attacker somehow knew part of (but not all) the contents of the buffer before hand,
/// they would not be able to predict any of the bits in the buffer at the end.
#[inline(always)]
fn update(&mut self, new_data: u64) {
self.buffer = folded_multiply(new_data ^ self.buffer, MULTIPLE);
}
/// Similar to the above this function performs an update using a "folded multiply".
/// However it takes in 128 bits of data instead of 64. Both halves must be masked.
///
/// This makes it impossible for an attacker to place a single bit difference between
/// two blocks so as to cancel each other.
///
/// However this is not sufficient. to prevent (a,b) from hashing the same as (b,a) the buffer itself must
/// be updated between calls in a way that does not commute. To achieve this XOR and Rotate are used.
/// Add followed by xor is not the same as xor followed by add, and rotate ensures that the same out bits
/// can't be changed by the same set of input bits. To cancel this sequence with subsequent input would require
/// knowing the keys.
#[inline(always)]
fn large_update(&mut self, new_data: u128) {
let block: [u64; 2] = new_data.convert();
let combined = folded_multiply(block[0] ^ self.extra_keys[0], block[1] ^ self.extra_keys[1]);
self.buffer = (self.buffer.wrapping_add(self.pad) ^ combined).rotate_left(ROT);
}
#[inline]
#[cfg(specialize)]
fn short_finish(&self) -> u64 {
folded_multiply(self.buffer, self.pad)
}
}
/// Provides [Hasher] methods to hash all of the primitive types.
///
/// [Hasher]: core::hash::Hasher
impl Hasher for AHasher {
#[inline]
fn write_u8(&mut self, i: u8) {
self.update(i as u64);
}
#[inline]
fn write_u16(&mut self, i: u16) {
self.update(i as u64);
}
#[inline]
fn write_u32(&mut self, i: u32) {
self.update(i as u64);
}
#[inline]
fn write_u64(&mut self, i: u64) {
self.update(i as u64);
}
#[inline]
fn write_u128(&mut self, i: u128) {
self.large_update(i);
}
#[inline]
#[cfg(any(
target_pointer_width = "64",
target_pointer_width = "32",
target_pointer_width = "16"
))]
fn write_usize(&mut self, i: usize) {
self.write_u64(i as u64);
}
#[inline]
#[cfg(target_pointer_width = "128")]
fn write_usize(&mut self, i: usize) {
self.write_u128(i as u128);
}
#[inline]
#[allow(clippy::collapsible_if)]
fn write(&mut self, input: &[u8]) {
let mut data = input;
let length = data.len() as u64;
//Needs to be an add rather than an xor because otherwise it could be canceled with carefully formed input.
self.buffer = self.buffer.wrapping_add(length).wrapping_mul(MULTIPLE);
//A 'binary search' on sizes reduces the number of comparisons.
if data.len() > 8 {
if data.len() > 16 {
let tail = data.read_last_u128();
self.large_update(tail);
while data.len() > 16 {
let (block, rest) = data.read_u128();
self.large_update(block);
data = rest;
}
} else {
self.large_update([data.read_u64().0, data.read_last_u64()].convert());
}
} else {
let value = read_small(data);
self.large_update(value.convert());
}
}
#[inline]
fn finish(&self) -> u64 {
let rot = (self.buffer & 63) as u32;
folded_multiply(self.buffer, self.pad).rotate_left(rot)
}
}
#[cfg(specialize)]
pub(crate) struct AHasherU64 {
pub(crate) buffer: u64,
pub(crate) pad: u64,
}
/// A specialized hasher for only primitives under 64 bits.
#[cfg(specialize)]
impl Hasher for AHasherU64 {
#[inline]
fn finish(&self) -> u64 {
folded_multiply(self.buffer, self.pad)
//self.buffer
}
#[inline]
fn write(&mut self, _bytes: &[u8]) {
unreachable!("Specialized hasher was called with a different type of object")
}
#[inline]
fn write_u8(&mut self, i: u8) {
self.write_u64(i as u64);
}
#[inline]
fn write_u16(&mut self, i: u16) {
self.write_u64(i as u64);
}
#[inline]
fn write_u32(&mut self, i: u32) {
self.write_u64(i as u64);
}
#[inline]
fn write_u64(&mut self, i: u64) {
self.buffer = folded_multiply(i ^ self.buffer, MULTIPLE);
}
#[inline]
fn write_u128(&mut self, _i: u128) {
unreachable!("Specialized hasher was called with a different type of object")
}
#[inline]
fn write_usize(&mut self, _i: usize) {
unreachable!("Specialized hasher was called with a different type of object")
}
}
#[cfg(specialize)]
pub(crate) struct AHasherFixed(pub AHasher);
/// A specialized hasher for fixed size primitives larger than 64 bits.
#[cfg(specialize)]
impl Hasher for AHasherFixed {
#[inline]
fn finish(&self) -> u64 {
self.0.short_finish()
}
#[inline]
fn write(&mut self, bytes: &[u8]) {
self.0.write(bytes)
}
#[inline]
fn write_u8(&mut self, i: u8) {
self.write_u64(i as u64);
}
#[inline]
fn write_u16(&mut self, i: u16) {
self.write_u64(i as u64);
}
#[inline]
fn write_u32(&mut self, i: u32) {
self.write_u64(i as u64);
}
#[inline]
fn write_u64(&mut self, i: u64) {
self.0.write_u64(i);
}
#[inline]
fn write_u128(&mut self, i: u128) {
self.0.write_u128(i);
}
#[inline]
fn write_usize(&mut self, i: usize) {
self.0.write_usize(i);
}
}
#[cfg(specialize)]
pub(crate) struct AHasherStr(pub AHasher);
/// A specialized hasher for a single string
/// Note that the other types don't panic because the hash impl for String tacks on an unneeded call. (As does vec)
#[cfg(specialize)]
impl Hasher for AHasherStr {
#[inline]
fn finish(&self) -> u64 {
self.0.finish()
}
#[inline]
fn write(&mut self, bytes: &[u8]) {
if bytes.len() > 8 {
self.0.write(bytes)
} else {
let value = read_small(bytes);
self.0.buffer = folded_multiply(value[0] ^ self.0.buffer, value[1] ^ self.0.extra_keys[1]);
self.0.pad = self.0.pad.wrapping_add(bytes.len() as u64);
}
}
#[inline]
fn write_u8(&mut self, _i: u8) {}
#[inline]
fn write_u16(&mut self, _i: u16) {}
#[inline]
fn write_u32(&mut self, _i: u32) {}
#[inline]
fn write_u64(&mut self, _i: u64) {}
#[inline]
fn write_u128(&mut self, _i: u128) {}
#[inline]
fn write_usize(&mut self, _i: usize) {}
}
#[cfg(test)]
mod tests {
use crate::fallback_hash::*;
#[test]
fn test_hash() {
let mut hasher = AHasher::new_with_keys(0, 0);
let value: u64 = 1 << 32;
hasher.update(value);
let result = hasher.buffer;
let mut hasher = AHasher::new_with_keys(0, 0);
let value2: u64 = 1;
hasher.update(value2);
let result2 = hasher.buffer;
let result: [u8; 8] = result.convert();
let result2: [u8; 8] = result2.convert();
assert_ne!(hex::encode(result), hex::encode(result2));
}
}
+501
View File
@@ -0,0 +1,501 @@
use std::borrow::Borrow;
use std::collections::hash_map::{IntoKeys, IntoValues};
use std::collections::{hash_map, HashMap};
use std::fmt::{self, Debug};
use std::hash::{BuildHasher, Hash};
use std::iter::FromIterator;
use std::ops::{Deref, DerefMut, Index};
use std::panic::UnwindSafe;
#[cfg(feature = "serde")]
use serde::{
de::{Deserialize, Deserializer},
ser::{Serialize, Serializer},
};
use crate::RandomState;
/// A [`HashMap`](std::collections::HashMap) using [`RandomState`](crate::RandomState) to hash the items.
/// (Requires the `std` feature to be enabled.)
#[derive(Clone)]
pub struct AHashMap<K, V, S = crate::RandomState>(HashMap<K, V, S>);
impl<K, V> From<HashMap<K, V, crate::RandomState>> for AHashMap<K, V> {
fn from(item: HashMap<K, V, crate::RandomState>) -> Self {
AHashMap(item)
}
}
impl<K, V, const N: usize> From<[(K, V); N]> for AHashMap<K, V>
where
K: Eq + Hash,
{
/// # Examples
///
/// ```
/// use ahash::AHashMap;
///
/// let map1 = AHashMap::from([(1, 2), (3, 4)]);
/// let map2: AHashMap<_, _> = [(1, 2), (3, 4)].into();
/// assert_eq!(map1, map2);
/// ```
fn from(arr: [(K, V); N]) -> Self {
Self::from_iter(arr)
}
}
impl<K, V> Into<HashMap<K, V, crate::RandomState>> for AHashMap<K, V> {
fn into(self) -> HashMap<K, V, crate::RandomState> {
self.0
}
}
impl<K, V> AHashMap<K, V, RandomState> {
/// This creates a hashmap using [RandomState::new] which obtains its keys from [RandomSource].
/// See the documentation in [RandomSource] for notes about key strength.
pub fn new() -> Self {
AHashMap(HashMap::with_hasher(RandomState::new()))
}
/// This creates a hashmap with the specified capacity using [RandomState::new].
/// See the documentation in [RandomSource] for notes about key strength.
pub fn with_capacity(capacity: usize) -> Self {
AHashMap(HashMap::with_capacity_and_hasher(capacity, RandomState::new()))
}
}
impl<K, V, S> AHashMap<K, V, S>
where
S: BuildHasher,
{
pub fn with_hasher(hash_builder: S) -> Self {
AHashMap(HashMap::with_hasher(hash_builder))
}
pub fn with_capacity_and_hasher(capacity: usize, hash_builder: S) -> Self {
AHashMap(HashMap::with_capacity_and_hasher(capacity, hash_builder))
}
}
impl<K, V, S> AHashMap<K, V, S>
where
K: Hash + Eq,
S: BuildHasher,
{
/// Returns a reference to the value corresponding to the key.
///
/// The key may be any borrowed form of the map's key type, but
/// [`Hash`] and [`Eq`] on the borrowed form *must* match those for
/// the key type.
///
/// # Examples
///
/// ```
/// use std::collections::HashMap;
///
/// let mut map = HashMap::new();
/// map.insert(1, "a");
/// assert_eq!(map.get(&1), Some(&"a"));
/// assert_eq!(map.get(&2), None);
/// ```
#[inline]
pub fn get<Q: ?Sized>(&self, k: &Q) -> Option<&V>
where
K: Borrow<Q>,
Q: Hash + Eq,
{
self.0.get(k)
}
/// Returns the key-value pair corresponding to the supplied key.
///
/// The supplied key may be any borrowed form of the map's key type, but
/// [`Hash`] and [`Eq`] on the borrowed form *must* match those for
/// the key type.
///
/// # Examples
///
/// ```
/// use std::collections::HashMap;
///
/// let mut map = HashMap::new();
/// map.insert(1, "a");
/// assert_eq!(map.get_key_value(&1), Some((&1, &"a")));
/// assert_eq!(map.get_key_value(&2), None);
/// ```
#[inline]
pub fn get_key_value<Q: ?Sized>(&self, k: &Q) -> Option<(&K, &V)>
where
K: Borrow<Q>,
Q: Hash + Eq,
{
self.0.get_key_value(k)
}
/// Returns a mutable reference to the value corresponding to the key.
///
/// The key may be any borrowed form of the map's key type, but
/// [`Hash`] and [`Eq`] on the borrowed form *must* match those for
/// the key type.
///
/// # Examples
///
/// ```
/// use std::collections::HashMap;
///
/// let mut map = HashMap::new();
/// map.insert(1, "a");
/// if let Some(x) = map.get_mut(&1) {
/// *x = "b";
/// }
/// assert_eq!(map[&1], "b");
/// ```
#[inline]
pub fn get_mut<Q: ?Sized>(&mut self, k: &Q) -> Option<&mut V>
where
K: Borrow<Q>,
Q: Hash + Eq,
{
self.0.get_mut(k)
}
/// Inserts a key-value pair into the map.
///
/// If the map did not have this key present, [`None`] is returned.
///
/// If the map did have this key present, the value is updated, and the old
/// value is returned. The key is not updated, though; this matters for
/// types that can be `==` without being identical. See the [module-level
/// documentation] for more.
///
/// # Examples
///
/// ```
/// use std::collections::HashMap;
///
/// let mut map = HashMap::new();
/// assert_eq!(map.insert(37, "a"), None);
/// assert_eq!(map.is_empty(), false);
///
/// map.insert(37, "b");
/// assert_eq!(map.insert(37, "c"), Some("b"));
/// assert_eq!(map[&37], "c");
/// ```
#[inline]
pub fn insert(&mut self, k: K, v: V) -> Option<V> {
self.0.insert(k, v)
}
/// Creates a consuming iterator visiting all the keys in arbitrary order.
/// The map cannot be used after calling this.
/// The iterator element type is `K`.
///
/// # Examples
///
/// ```
/// use std::collections::HashMap;
///
/// let map = HashMap::from([
/// ("a", 1),
/// ("b", 2),
/// ("c", 3),
/// ]);
///
/// let mut vec: Vec<&str> = map.into_keys().collect();
/// // The `IntoKeys` iterator produces keys in arbitrary order, so the
/// // keys must be sorted to test them against a sorted array.
/// vec.sort_unstable();
/// assert_eq!(vec, ["a", "b", "c"]);
/// ```
///
/// # Performance
///
/// In the current implementation, iterating over keys takes O(capacity) time
/// instead of O(len) because it internally visits empty buckets too.
#[inline]
pub fn into_keys(self) -> IntoKeys<K, V> {
self.0.into_keys()
}
/// Creates a consuming iterator visiting all the values in arbitrary order.
/// The map cannot be used after calling this.
/// The iterator element type is `V`.
///
/// # Examples
///
/// ```
/// use std::collections::HashMap;
///
/// let map = HashMap::from([
/// ("a", 1),
/// ("b", 2),
/// ("c", 3),
/// ]);
///
/// let mut vec: Vec<i32> = map.into_values().collect();
/// // The `IntoValues` iterator produces values in arbitrary order, so
/// // the values must be sorted to test them against a sorted array.
/// vec.sort_unstable();
/// assert_eq!(vec, [1, 2, 3]);
/// ```
///
/// # Performance
///
/// In the current implementation, iterating over values takes O(capacity) time
/// instead of O(len) because it internally visits empty buckets too.
#[inline]
pub fn into_values(self) -> IntoValues<K, V> {
self.0.into_values()
}
/// Removes a key from the map, returning the value at the key if the key
/// was previously in the map.
///
/// The key may be any borrowed form of the map's key type, but
/// [`Hash`] and [`Eq`] on the borrowed form *must* match those for
/// the key type.
///
/// # Examples
///
/// ```
/// use std::collections::HashMap;
///
/// let mut map = HashMap::new();
/// map.insert(1, "a");
/// assert_eq!(map.remove(&1), Some("a"));
/// assert_eq!(map.remove(&1), None);
/// ```
#[inline]
pub fn remove<Q: ?Sized>(&mut self, k: &Q) -> Option<V>
where
K: Borrow<Q>,
Q: Hash + Eq,
{
self.0.remove(k)
}
}
impl<K, V, S> Deref for AHashMap<K, V, S> {
type Target = HashMap<K, V, S>;
fn deref(&self) -> &Self::Target {
&self.0
}
}
impl<K, V, S> DerefMut for AHashMap<K, V, S> {
fn deref_mut(&mut self) -> &mut Self::Target {
&mut self.0
}
}
impl<K, V, S> UnwindSafe for AHashMap<K, V, S>
where
K: UnwindSafe,
V: UnwindSafe,
{
}
impl<K, V, S> PartialEq for AHashMap<K, V, S>
where
K: Eq + Hash,
V: PartialEq,
S: BuildHasher,
{
fn eq(&self, other: &AHashMap<K, V, S>) -> bool {
self.0.eq(&other.0)
}
}
impl<K, V, S> Eq for AHashMap<K, V, S>
where
K: Eq + Hash,
V: Eq,
S: BuildHasher,
{
}
impl<K, Q: ?Sized, V, S> Index<&Q> for AHashMap<K, V, S>
where
K: Eq + Hash + Borrow<Q>,
Q: Eq + Hash,
S: BuildHasher,
{
type Output = V;
/// Returns a reference to the value corresponding to the supplied key.
///
/// # Panics
///
/// Panics if the key is not present in the `HashMap`.
#[inline]
fn index(&self, key: &Q) -> &V {
self.0.index(key)
}
}
impl<K, V, S> Debug for AHashMap<K, V, S>
where
K: Debug,
V: Debug,
S: BuildHasher,
{
fn fmt(&self, fmt: &mut fmt::Formatter) -> fmt::Result {
self.0.fmt(fmt)
}
}
impl<K, V> FromIterator<(K, V)> for AHashMap<K, V, RandomState>
where
K: Eq + Hash,
{
/// This creates a hashmap from the provided iterator using [RandomState::new].
/// See the documentation in [RandomSource] for notes about key strength.
fn from_iter<T: IntoIterator<Item = (K, V)>>(iter: T) -> Self {
let mut inner = HashMap::with_hasher(RandomState::new());
inner.extend(iter);
AHashMap(inner)
}
}
impl<'a, K, V, S> IntoIterator for &'a AHashMap<K, V, S> {
type Item = (&'a K, &'a V);
type IntoIter = hash_map::Iter<'a, K, V>;
fn into_iter(self) -> Self::IntoIter {
(&self.0).iter()
}
}
impl<'a, K, V, S> IntoIterator for &'a mut AHashMap<K, V, S> {
type Item = (&'a K, &'a mut V);
type IntoIter = hash_map::IterMut<'a, K, V>;
fn into_iter(self) -> Self::IntoIter {
(&mut self.0).iter_mut()
}
}
impl<K, V, S> IntoIterator for AHashMap<K, V, S> {
type Item = (K, V);
type IntoIter = hash_map::IntoIter<K, V>;
fn into_iter(self) -> Self::IntoIter {
self.0.into_iter()
}
}
impl<K, V, S> Extend<(K, V)> for AHashMap<K, V, S>
where
K: Eq + Hash,
S: BuildHasher,
{
#[inline]
fn extend<T: IntoIterator<Item = (K, V)>>(&mut self, iter: T) {
self.0.extend(iter)
}
}
impl<'a, K, V, S> Extend<(&'a K, &'a V)> for AHashMap<K, V, S>
where
K: Eq + Hash + Copy + 'a,
V: Copy + 'a,
S: BuildHasher,
{
#[inline]
fn extend<T: IntoIterator<Item = (&'a K, &'a V)>>(&mut self, iter: T) {
self.0.extend(iter)
}
}
/// NOTE: For safety this trait impl is only available if either of the flags `runtime-rng` (on by default) or
/// `compile-time-rng` are enabled. This is to prevent weakly keyed maps from being accidentally created. Instead one of
/// constructors for [RandomState] must be used.
#[cfg(any(feature = "compile-time-rng", feature = "runtime-rng", feature = "no-rng"))]
impl<K, V> Default for AHashMap<K, V, RandomState> {
#[inline]
fn default() -> AHashMap<K, V, RandomState> {
AHashMap(HashMap::default())
}
}
#[cfg(feature = "serde")]
impl<K, V> Serialize for AHashMap<K, V>
where
K: Serialize + Eq + Hash,
V: Serialize,
{
fn serialize<S: Serializer>(&self, serializer: S) -> Result<S::Ok, S::Error> {
self.deref().serialize(serializer)
}
}
#[cfg(feature = "serde")]
impl<'de, K, V> Deserialize<'de> for AHashMap<K, V>
where
K: Deserialize<'de> + Eq + Hash,
V: Deserialize<'de>,
{
fn deserialize<D: Deserializer<'de>>(deserializer: D) -> Result<Self, D::Error> {
let hash_map = HashMap::deserialize(deserializer);
hash_map.map(|hash_map| Self(hash_map))
}
fn deserialize_in_place<D: Deserializer<'de>>(deserializer: D, place: &mut Self) -> Result<(), D::Error> {
use serde::de::{MapAccess, Visitor};
struct MapInPlaceVisitor<'a, K: 'a, V: 'a>(&'a mut AHashMap<K, V>);
impl<'a, 'de, K, V> Visitor<'de> for MapInPlaceVisitor<'a, K, V>
where
K: Deserialize<'de> + Eq + Hash,
V: Deserialize<'de>,
{
type Value = ();
fn expecting(&self, formatter: &mut fmt::Formatter) -> fmt::Result {
formatter.write_str("a map")
}
fn visit_map<A>(self, mut map: A) -> Result<Self::Value, A::Error>
where
A: MapAccess<'de>,
{
self.0.clear();
self.0.reserve(map.size_hint().unwrap_or(0).min(4096));
while let Some((key, value)) = map.next_entry()? {
self.0.insert(key, value);
}
Ok(())
}
}
deserializer.deserialize_map(MapInPlaceVisitor(place))
}
}
#[cfg(test)]
mod test {
use super::*;
#[test]
fn test_borrow() {
let mut map: AHashMap<String, String> = AHashMap::new();
map.insert("foo".to_string(), "Bar".to_string());
map.insert("Bar".to_string(), map.get("foo").unwrap().to_owned());
}
#[cfg(feature = "serde")]
#[test]
fn test_serde() {
let mut map = AHashMap::new();
map.insert("for".to_string(), 0);
map.insert("bar".to_string(), 1);
let mut serialization = serde_json::to_string(&map).unwrap();
let mut deserialization: AHashMap<String, u64> = serde_json::from_str(&serialization).unwrap();
assert_eq!(deserialization, map);
map.insert("baz".to_string(), 2);
serialization = serde_json::to_string(&map).unwrap();
let mut deserializer = serde_json::Deserializer::from_str(&serialization);
AHashMap::deserialize_in_place(&mut deserializer, &mut deserialization).unwrap();
assert_eq!(deserialization, map);
}
}
+534
View File
@@ -0,0 +1,534 @@
use core::hash::{Hash, Hasher};
use std::collections::HashMap;
fn assert_sufficiently_different(a: u64, b: u64, tolerance: i32) {
let (same_byte_count, same_nibble_count) = count_same_bytes_and_nibbles(a, b);
assert!(same_byte_count <= tolerance, "{:x} vs {:x}: {:}", a, b, same_byte_count);
assert!(
same_nibble_count <= tolerance * 3,
"{:x} vs {:x}: {:}",
a,
b,
same_nibble_count
);
let flipped_bits = (a ^ b).count_ones();
assert!(
flipped_bits > 12 && flipped_bits < 52,
"{:x} and {:x}: {:}",
a,
b,
flipped_bits
);
for rotate in 0..64 {
let flipped_bits2 = (a ^ (b.rotate_left(rotate))).count_ones();
assert!(
flipped_bits2 > 10 && flipped_bits2 < 54,
"{:x} and {:x}: {:}",
a,
b.rotate_left(rotate),
flipped_bits2
);
}
}
fn count_same_bytes_and_nibbles(a: u64, b: u64) -> (i32, i32) {
let mut same_byte_count = 0;
let mut same_nibble_count = 0;
for byte in 0..8 {
let ba = (a >> (8 * byte)) as u8;
let bb = (b >> (8 * byte)) as u8;
if ba == bb {
same_byte_count += 1;
}
if ba & 0xF0u8 == bb & 0xF0u8 {
same_nibble_count += 1;
}
if ba & 0x0Fu8 == bb & 0x0Fu8 {
same_nibble_count += 1;
}
}
(same_byte_count, same_nibble_count)
}
fn gen_combinations(options: &[u32; 11], depth: u32, so_far: Vec<u32>, combinations: &mut Vec<Vec<u32>>) {
if depth == 0 {
return;
}
for option in options {
let mut next = so_far.clone();
next.push(*option);
combinations.push(next.clone());
gen_combinations(options, depth - 1, next, combinations);
}
}
fn test_no_full_collisions<T: Hasher>(gen_hash: impl Fn() -> T) {
let options: [u32; 11] = [
0x00000000, 0x10000000, 0x20000000, 0x40000000, 0x80000000, 0xF0000000, 1, 2, 4, 8, 15,
];
let mut combinations = Vec::new();
gen_combinations(&options, 7, Vec::new(), &mut combinations);
let mut map: HashMap<u64, Vec<u8>> = HashMap::new();
for combination in combinations {
use zerocopy::IntoBytes;
let array = combination.as_bytes().to_vec();
let mut hasher = gen_hash();
hasher.write(&array);
let hash = hasher.finish();
if let Some(value) = map.get(&hash) {
assert_eq!(
value, &array,
"Found a collision between {:x?} and {:x?}. Hash: {:x?}",
value, &array, &hash
);
} else {
map.insert(hash, array);
}
}
assert_eq!(21435887, map.len()); //11^7 + 11^6 ...
}
fn test_keys_change_output<T: Hasher>(constructor: impl Fn(u128, u128) -> T) {
let mut a = constructor(1, 1);
let mut b = constructor(1, 2);
let mut c = constructor(2, 1);
let mut d = constructor(2, 2);
"test".hash(&mut a);
"test".hash(&mut b);
"test".hash(&mut c);
"test".hash(&mut d);
assert_sufficiently_different(a.finish(), b.finish(), 1);
assert_sufficiently_different(a.finish(), c.finish(), 1);
assert_sufficiently_different(a.finish(), d.finish(), 1);
assert_sufficiently_different(b.finish(), c.finish(), 1);
assert_sufficiently_different(b.finish(), d.finish(), 1);
assert_sufficiently_different(c.finish(), d.finish(), 1);
}
fn test_input_affect_every_byte<T: Hasher>(constructor: impl Fn(u128, u128) -> T) {
let base = hash_with(&0, constructor(0, 0));
for shift in 0..16 {
let mut alternatives = vec![];
for v in 0..256 {
let input = (v as u128) << (shift * 8);
let hasher = constructor(0, 0);
alternatives.push(hash_with(&input, hasher));
}
assert_each_byte_differs(shift, base, alternatives);
}
}
///Ensures that for every bit in the output there is some value for each byte in the key that flips it.
fn test_keys_affect_every_byte<H: Hash, T: Hasher>(item: H, constructor: impl Fn(u128, u128) -> T) {
let base = hash_with(&item, constructor(0, 0));
for shift in 0..16 {
let mut alternatives1 = vec![];
let mut alternatives2 = vec![];
for v in 0..256 {
let input = (v as u128) << (shift * 8);
let hasher1 = constructor(input, 0);
let hasher2 = constructor(0, input);
let h1 = hash_with(&item, hasher1);
let h2 = hash_with(&item, hasher2);
alternatives1.push(h1);
alternatives2.push(h2);
}
assert_each_byte_differs(shift, base, alternatives1);
assert_each_byte_differs(shift, base, alternatives2);
}
}
fn assert_each_byte_differs(num: u64, base: u64, alternatives: Vec<u64>) {
let mut changed_bits = 0_u64;
for alternative in alternatives {
changed_bits |= base ^ alternative
}
assert_eq!(
core::u64::MAX,
changed_bits,
"Bits changed: {:x} on num: {:?}. base {:x}",
changed_bits,
num,
base
);
}
fn test_finish_is_consistent<T: Hasher>(constructor: impl Fn(u128, u128) -> T) {
let mut hasher = constructor(1, 2);
"Foo".hash(&mut hasher);
let a = hasher.finish();
let b = hasher.finish();
assert_eq!(a, b);
}
fn test_single_key_bit_flip<T: Hasher>(constructor: impl Fn(u128, u128) -> T) {
for bit in 0..128 {
let mut a = constructor(0, 0);
let mut b = constructor(0, 1 << bit);
let mut c = constructor(1 << bit, 0);
"1234".hash(&mut a);
"1234".hash(&mut b);
"1234".hash(&mut c);
assert_sufficiently_different(a.finish(), b.finish(), 2);
assert_sufficiently_different(a.finish(), c.finish(), 2);
assert_sufficiently_different(b.finish(), c.finish(), 2);
let mut a = constructor(0, 0);
let mut b = constructor(0, 1 << bit);
let mut c = constructor(1 << bit, 0);
"12345678".hash(&mut a);
"12345678".hash(&mut b);
"12345678".hash(&mut c);
assert_sufficiently_different(a.finish(), b.finish(), 2);
assert_sufficiently_different(a.finish(), c.finish(), 2);
assert_sufficiently_different(b.finish(), c.finish(), 2);
let mut a = constructor(0, 0);
let mut b = constructor(0, 1 << bit);
let mut c = constructor(1 << bit, 0);
"1234567812345678".hash(&mut a);
"1234567812345678".hash(&mut b);
"1234567812345678".hash(&mut c);
assert_sufficiently_different(a.finish(), b.finish(), 2);
assert_sufficiently_different(a.finish(), c.finish(), 2);
assert_sufficiently_different(b.finish(), c.finish(), 2);
}
}
fn test_all_bytes_matter<T: Hasher>(hasher: impl Fn() -> T) {
let mut item = vec![0; 256];
let base_hash = hash(&item, &hasher);
for pos in 0..256 {
item[pos] = 255;
let hash = hash(&item, &hasher);
assert_ne!(base_hash, hash, "Position {} did not affect output", pos);
item[pos] = 0;
}
}
fn test_no_pair_collisions<T: Hasher>(hasher: impl Fn() -> T) {
let base = [0_u64, 0_u64];
let base_hash = hash(&base, &hasher);
for bitpos1 in 0..64 {
let a = 1_u64 << bitpos1;
for bitpos2 in 0..bitpos1 {
let b = 1_u64 << bitpos2;
let aa = hash(&[a, a], &hasher);
let ab = hash(&[a, b], &hasher);
let ba = hash(&[b, a], &hasher);
let bb = hash(&[b, b], &hasher);
assert_sufficiently_different(base_hash, aa, 3);
assert_sufficiently_different(base_hash, ab, 3);
assert_sufficiently_different(base_hash, ba, 3);
assert_sufficiently_different(base_hash, bb, 3);
assert_sufficiently_different(aa, ab, 3);
assert_sufficiently_different(ab, ba, 3);
assert_sufficiently_different(ba, bb, 3);
assert_sufficiently_different(aa, ba, 3);
assert_sufficiently_different(ab, bb, 3);
assert_sufficiently_different(aa, bb, 3);
}
}
}
fn hash<H: Hash, T: Hasher>(b: &H, hash_builder: &dyn Fn() -> T) -> u64 {
let mut hasher = hash_builder();
b.hash(&mut hasher);
hasher.finish()
}
fn hash_with<H: Hash, T: Hasher>(b: &H, mut hasher: T) -> u64 {
b.hash(&mut hasher);
hasher.finish()
}
fn test_single_bit_flip<T: Hasher>(hasher: impl Fn() -> T) {
let size = 32;
let compare_value = hash(&0u32, &hasher);
for pos in 0..size {
let test_value = hash(&(1u32 << pos), &hasher);
assert_sufficiently_different(compare_value, test_value, 2);
}
let size = 64;
let compare_value = hash(&0u64, &hasher);
for pos in 0..size {
let test_value = hash(&(1u64 << pos), &hasher);
assert_sufficiently_different(compare_value, test_value, 2);
}
let size = 128;
let compare_value = hash(&0u128, &hasher);
for pos in 0..size {
let test_value = hash(&(1u128 << pos), &hasher);
dbg!(compare_value, test_value);
assert_sufficiently_different(compare_value, test_value, 2);
}
}
fn test_padding_doesnot_collide<T: Hasher>(hasher: impl Fn() -> T) {
for c in 0..128u8 {
for string in ["", "\0", "\x01", "1234", "12345678", "1234567812345678"].iter() {
let mut short = hasher();
string.hash(&mut short);
let value = short.finish();
let mut padded = string.to_string();
for num in 1..=128 {
let mut long = hasher();
padded.push(c as char);
padded.hash(&mut long);
let (same_bytes, same_nibbles) = count_same_bytes_and_nibbles(value, long.finish());
assert!(
same_bytes <= 3,
"{} bytes of {} -> {:x} vs {:x}",
num,
c,
value,
long.finish()
);
assert!(
same_nibbles <= 8,
"{} bytes of {} -> {:x} vs {:x}",
num,
c,
value,
long.finish()
);
let flipped_bits = (value ^ long.finish()).count_ones();
assert!(flipped_bits > 10);
}
if string.len() > 0 {
let mut padded = string[1..].to_string();
padded.push(c as char);
for num in 2..=128 {
let mut long = hasher();
padded.push(c as char);
padded.hash(&mut long);
let (same_bytes, same_nibbles) = count_same_bytes_and_nibbles(value, long.finish());
assert!(
same_bytes <= 3,
"string {:?} + {} bytes of {} -> {:x} vs {:x}",
string,
num,
c,
value,
long.finish()
);
assert!(
same_nibbles <= 8,
"string {:?} + {} bytes of {} -> {:x} vs {:x}",
string,
num,
c,
value,
long.finish()
);
let flipped_bits = (value ^ long.finish()).count_ones();
assert!(flipped_bits > 10);
}
}
}
}
}
fn test_length_extension<T: Hasher>(hasher: impl Fn(u128, u128) -> T) {
for key in 0..256 {
let h1 = hasher(key, key);
let v1 = hash_with(&[0_u8, 0, 0, 0, 0, 0, 0, 0], h1);
let h2 = hasher(key, key);
let v2 = hash_with(&[1_u8, 0, 0, 0, 0, 0, 0, 0, 0], h2);
assert_ne!(v1, v2);
}
}
fn test_sparse<T: Hasher>(hasher: impl Fn() -> T) {
use smallvec::SmallVec;
let mut buf = [0u8; 256];
let mut hashes = HashMap::new();
for idx_1 in 0..255_u8 {
for idx_2 in idx_1 + 1..=255_u8 {
for value_1 in [1, 2, 4, 8, 16, 32, 64, 128] {
for value_2 in [
1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 12, 15, 16, 17, 18, 20, 24, 31, 32, 33, 48, 64, 96, 127, 128, 129,
192, 254, 255,
] {
buf[idx_1 as usize] = value_1;
buf[idx_2 as usize] = value_2;
let hash_value = hash_with(&buf, &mut hasher());
let keys = hashes.entry(hash_value).or_insert(SmallVec::<[[u8; 4]; 1]>::new());
keys.push([idx_1, value_1, idx_2, value_2]);
buf[idx_1 as usize] = 0;
buf[idx_2 as usize] = 0;
}
}
}
}
hashes.retain(|_key, value| value.len() != 1);
assert_eq!(0, hashes.len(), "Collision with: {:?}", hashes);
}
#[cfg(test)]
mod fallback_tests {
use crate::fallback_hash::*;
use crate::hash_quality_test::*;
#[test]
fn fallback_single_bit_flip() {
test_single_bit_flip(|| AHasher::new_with_keys(0, 0))
}
#[test]
fn fallback_single_key_bit_flip() {
test_single_key_bit_flip(AHasher::new_with_keys)
}
#[test]
fn fallback_all_bytes_matter() {
test_all_bytes_matter(|| AHasher::new_with_keys(0, 0));
}
#[test]
fn fallback_test_no_pair_collisions() {
test_no_pair_collisions(|| AHasher::new_with_keys(0, 0));
}
#[test]
fn fallback_test_no_full_collisions() {
test_no_full_collisions(|| AHasher::new_with_keys(0, 0));
}
#[test]
fn fallback_keys_change_output() {
test_keys_change_output(AHasher::new_with_keys);
}
#[test]
fn fallback_input_affect_every_byte() {
test_input_affect_every_byte(AHasher::new_with_keys);
}
#[test]
fn fallback_keys_affect_every_byte() {
//For fallback second key is not used in every hash.
#[cfg(all(not(specialize), folded_multiply))]
test_keys_affect_every_byte(0, |a, b| AHasher::new_with_keys(a ^ b, a));
test_keys_affect_every_byte("", |a, b| AHasher::new_with_keys(a ^ b, a));
test_keys_affect_every_byte((0, 0), |a, b| AHasher::new_with_keys(a ^ b, a));
}
#[test]
fn fallback_finish_is_consistant() {
test_finish_is_consistent(AHasher::test_with_keys)
}
#[test]
fn fallback_padding_doesnot_collide() {
test_padding_doesnot_collide(|| AHasher::new_with_keys(0, 0));
test_padding_doesnot_collide(|| AHasher::new_with_keys(0, 2));
test_padding_doesnot_collide(|| AHasher::new_with_keys(2, 0));
test_padding_doesnot_collide(|| AHasher::new_with_keys(2, 2));
}
#[test]
fn fallback_length_extension() {
test_length_extension(|a, b| AHasher::new_with_keys(a, b));
}
#[test]
fn test_no_sparse_collisions() {
test_sparse(|| AHasher::new_with_keys(0, 0));
test_sparse(|| AHasher::new_with_keys(1, 2));
}
}
///Basic sanity tests of the cypto properties of aHash.
#[cfg(any(
all(any(target_arch = "x86", target_arch = "x86_64"), target_feature = "aes", not(miri)),
all(feature = "nightly-arm-aes", target_arch = "aarch64", target_feature = "aes", not(miri)),
all(feature = "nightly-arm-aes", target_arch = "arm", target_feature = "aes", not(miri)),
))]
#[cfg(test)]
mod aes_tests {
use crate::aes_hash::*;
use crate::hash_quality_test::*;
use std::hash::{Hash, Hasher};
//This encrypts to 0.
const BAD_KEY2: u128 = 0x6363_6363_6363_6363_6363_6363_6363_6363;
//This decrypts to 0.
const BAD_KEY: u128 = 0x5252_5252_5252_5252_5252_5252_5252_5252;
#[test]
fn test_single_bit_in_byte() {
let mut hasher1 = AHasher::test_with_keys(0, 0);
8_u32.hash(&mut hasher1);
let mut hasher2 = AHasher::test_with_keys(0, 0);
0_u32.hash(&mut hasher2);
assert_sufficiently_different(hasher1.finish(), hasher2.finish(), 1);
}
#[test]
fn aes_single_bit_flip() {
test_single_bit_flip(|| AHasher::test_with_keys(BAD_KEY, BAD_KEY));
test_single_bit_flip(|| AHasher::test_with_keys(BAD_KEY2, BAD_KEY2));
}
#[test]
fn aes_single_key_bit_flip() {
test_single_key_bit_flip(AHasher::test_with_keys)
}
#[test]
fn aes_all_bytes_matter() {
test_all_bytes_matter(|| AHasher::test_with_keys(BAD_KEY, BAD_KEY));
test_all_bytes_matter(|| AHasher::test_with_keys(BAD_KEY2, BAD_KEY2));
}
#[test]
fn aes_test_no_pair_collisions() {
test_no_pair_collisions(|| AHasher::test_with_keys(BAD_KEY, BAD_KEY));
test_no_pair_collisions(|| AHasher::test_with_keys(BAD_KEY2, BAD_KEY2));
}
#[test]
fn ase_test_no_full_collisions() {
test_no_full_collisions(|| AHasher::test_with_keys(12345, 67890));
}
#[test]
fn aes_keys_change_output() {
test_keys_change_output(AHasher::test_with_keys);
}
#[test]
fn aes_input_affect_every_byte() {
test_input_affect_every_byte(AHasher::test_with_keys);
}
#[test]
fn aes_keys_affect_every_byte() {
#[cfg(not(specialize))]
test_keys_affect_every_byte(0, AHasher::test_with_keys);
test_keys_affect_every_byte("", AHasher::test_with_keys);
test_keys_affect_every_byte((0, 0), AHasher::test_with_keys);
}
#[test]
fn aes_finish_is_consistant() {
test_finish_is_consistent(AHasher::test_with_keys)
}
#[test]
fn aes_padding_doesnot_collide() {
test_padding_doesnot_collide(|| AHasher::test_with_keys(BAD_KEY, BAD_KEY));
test_padding_doesnot_collide(|| AHasher::test_with_keys(BAD_KEY2, BAD_KEY2));
}
#[test]
fn aes_length_extension() {
test_length_extension(|a, b| AHasher::test_with_keys(a, b));
}
#[test]
fn aes_no_sparse_collisions() {
test_sparse(|| AHasher::test_with_keys(0, 0));
test_sparse(|| AHasher::test_with_keys(1, 2));
}
}
+352
View File
@@ -0,0 +1,352 @@
use crate::RandomState;
use std::collections::{hash_set, HashSet};
use std::fmt::{self, Debug};
use std::hash::{BuildHasher, Hash};
use std::iter::FromIterator;
use std::ops::{BitAnd, BitOr, BitXor, Deref, DerefMut, Sub};
#[cfg(feature = "serde")]
use serde::{
de::{Deserialize, Deserializer},
ser::{Serialize, Serializer},
};
/// A [`HashSet`](std::collections::HashSet) using [`RandomState`](crate::RandomState) to hash the items.
/// (Requires the `std` feature to be enabled.)
#[derive(Clone)]
pub struct AHashSet<T, S = RandomState>(HashSet<T, S>);
impl<T> From<HashSet<T, RandomState>> for AHashSet<T> {
fn from(item: HashSet<T, RandomState>) -> Self {
AHashSet(item)
}
}
impl<T, const N: usize> From<[T; N]> for AHashSet<T>
where
T: Eq + Hash,
{
/// # Examples
///
/// ```
/// use ahash::AHashSet;
///
/// let set1 = AHashSet::from([1, 2, 3, 4]);
/// let set2: AHashSet<_> = [1, 2, 3, 4].into();
/// assert_eq!(set1, set2);
/// ```
fn from(arr: [T; N]) -> Self {
Self::from_iter(arr)
}
}
impl<T> Into<HashSet<T, RandomState>> for AHashSet<T> {
fn into(self) -> HashSet<T, RandomState> {
self.0
}
}
impl<T> AHashSet<T, RandomState> {
/// This creates a hashset using [RandomState::new].
/// See the documentation in [RandomSource] for notes about key strength.
pub fn new() -> Self {
AHashSet(HashSet::with_hasher(RandomState::new()))
}
/// This craetes a hashset with the specified capacity using [RandomState::new].
/// See the documentation in [RandomSource] for notes about key strength.
pub fn with_capacity(capacity: usize) -> Self {
AHashSet(HashSet::with_capacity_and_hasher(capacity, RandomState::new()))
}
}
impl<T, S> AHashSet<T, S>
where
S: BuildHasher,
{
pub fn with_hasher(hash_builder: S) -> Self {
AHashSet(HashSet::with_hasher(hash_builder))
}
pub fn with_capacity_and_hasher(capacity: usize, hash_builder: S) -> Self {
AHashSet(HashSet::with_capacity_and_hasher(capacity, hash_builder))
}
}
impl<T, S> Deref for AHashSet<T, S> {
type Target = HashSet<T, S>;
fn deref(&self) -> &Self::Target {
&self.0
}
}
impl<T, S> DerefMut for AHashSet<T, S> {
fn deref_mut(&mut self) -> &mut Self::Target {
&mut self.0
}
}
impl<T, S> PartialEq for AHashSet<T, S>
where
T: Eq + Hash,
S: BuildHasher,
{
fn eq(&self, other: &AHashSet<T, S>) -> bool {
self.0.eq(&other.0)
}
}
impl<T, S> Eq for AHashSet<T, S>
where
T: Eq + Hash,
S: BuildHasher,
{
}
impl<T, S> BitOr<&AHashSet<T, S>> for &AHashSet<T, S>
where
T: Eq + Hash + Clone,
S: BuildHasher + Default,
{
type Output = AHashSet<T, S>;
/// Returns the union of `self` and `rhs` as a new `AHashSet<T, S>`.
///
/// # Examples
///
/// ```
/// use ahash::AHashSet;
///
/// let a: AHashSet<_> = vec![1, 2, 3].into_iter().collect();
/// let b: AHashSet<_> = vec![3, 4, 5].into_iter().collect();
///
/// let set = &a | &b;
///
/// let mut i = 0;
/// let expected = [1, 2, 3, 4, 5];
/// for x in &set {
/// assert!(expected.contains(x));
/// i += 1;
/// }
/// assert_eq!(i, expected.len());
/// ```
fn bitor(self, rhs: &AHashSet<T, S>) -> AHashSet<T, S> {
AHashSet(self.0.bitor(&rhs.0))
}
}
impl<T, S> BitAnd<&AHashSet<T, S>> for &AHashSet<T, S>
where
T: Eq + Hash + Clone,
S: BuildHasher + Default,
{
type Output = AHashSet<T, S>;
/// Returns the intersection of `self` and `rhs` as a new `AHashSet<T, S>`.
///
/// # Examples
///
/// ```
/// use ahash::AHashSet;
///
/// let a: AHashSet<_> = vec![1, 2, 3].into_iter().collect();
/// let b: AHashSet<_> = vec![2, 3, 4].into_iter().collect();
///
/// let set = &a & &b;
///
/// let mut i = 0;
/// let expected = [2, 3];
/// for x in &set {
/// assert!(expected.contains(x));
/// i += 1;
/// }
/// assert_eq!(i, expected.len());
/// ```
fn bitand(self, rhs: &AHashSet<T, S>) -> AHashSet<T, S> {
AHashSet(self.0.bitand(&rhs.0))
}
}
impl<T, S> BitXor<&AHashSet<T, S>> for &AHashSet<T, S>
where
T: Eq + Hash + Clone,
S: BuildHasher + Default,
{
type Output = AHashSet<T, S>;
/// Returns the symmetric difference of `self` and `rhs` as a new `AHashSet<T, S>`.
///
/// # Examples
///
/// ```
/// use ahash::AHashSet;
///
/// let a: AHashSet<_> = vec![1, 2, 3].into_iter().collect();
/// let b: AHashSet<_> = vec![3, 4, 5].into_iter().collect();
///
/// let set = &a ^ &b;
///
/// let mut i = 0;
/// let expected = [1, 2, 4, 5];
/// for x in &set {
/// assert!(expected.contains(x));
/// i += 1;
/// }
/// assert_eq!(i, expected.len());
/// ```
fn bitxor(self, rhs: &AHashSet<T, S>) -> AHashSet<T, S> {
AHashSet(self.0.bitxor(&rhs.0))
}
}
impl<T, S> Sub<&AHashSet<T, S>> for &AHashSet<T, S>
where
T: Eq + Hash + Clone,
S: BuildHasher + Default,
{
type Output = AHashSet<T, S>;
/// Returns the difference of `self` and `rhs` as a new `AHashSet<T, S>`.
///
/// # Examples
///
/// ```
/// use ahash::AHashSet;
///
/// let a: AHashSet<_> = vec![1, 2, 3].into_iter().collect();
/// let b: AHashSet<_> = vec![3, 4, 5].into_iter().collect();
///
/// let set = &a - &b;
///
/// let mut i = 0;
/// let expected = [1, 2];
/// for x in &set {
/// assert!(expected.contains(x));
/// i += 1;
/// }
/// assert_eq!(i, expected.len());
/// ```
fn sub(self, rhs: &AHashSet<T, S>) -> AHashSet<T, S> {
AHashSet(self.0.sub(&rhs.0))
}
}
impl<T, S> Debug for AHashSet<T, S>
where
T: Debug,
S: BuildHasher,
{
fn fmt(&self, fmt: &mut fmt::Formatter<'_>) -> fmt::Result {
self.0.fmt(fmt)
}
}
impl<T> FromIterator<T> for AHashSet<T, RandomState>
where
T: Eq + Hash,
{
/// This creates a hashset from the provided iterator using [RandomState::new].
/// See the documentation in [RandomSource] for notes about key strength.
#[inline]
fn from_iter<I: IntoIterator<Item = T>>(iter: I) -> AHashSet<T> {
let mut inner = HashSet::with_hasher(RandomState::new());
inner.extend(iter);
AHashSet(inner)
}
}
impl<'a, T, S> IntoIterator for &'a AHashSet<T, S> {
type Item = &'a T;
type IntoIter = hash_set::Iter<'a, T>;
fn into_iter(self) -> Self::IntoIter {
(&self.0).iter()
}
}
impl<T, S> IntoIterator for AHashSet<T, S> {
type Item = T;
type IntoIter = hash_set::IntoIter<T>;
fn into_iter(self) -> Self::IntoIter {
self.0.into_iter()
}
}
impl<T, S> Extend<T> for AHashSet<T, S>
where
T: Eq + Hash,
S: BuildHasher,
{
#[inline]
fn extend<I: IntoIterator<Item = T>>(&mut self, iter: I) {
self.0.extend(iter)
}
}
impl<'a, T, S> Extend<&'a T> for AHashSet<T, S>
where
T: 'a + Eq + Hash + Copy,
S: BuildHasher,
{
#[inline]
fn extend<I: IntoIterator<Item = &'a T>>(&mut self, iter: I) {
self.0.extend(iter)
}
}
/// NOTE: For safety this trait impl is only available available if either of the flags `runtime-rng` (on by default) or
/// `compile-time-rng` are enabled. This is to prevent weakly keyed maps from being accidentally created. Instead one of
/// constructors for [RandomState] must be used.
#[cfg(any(feature = "compile-time-rng", feature = "runtime-rng", feature = "no-rng"))]
impl<T> Default for AHashSet<T, RandomState> {
/// Creates an empty `AHashSet<T, S>` with the `Default` value for the hasher.
#[inline]
fn default() -> AHashSet<T, RandomState> {
AHashSet(HashSet::default())
}
}
#[cfg(feature = "serde")]
impl<T> Serialize for AHashSet<T>
where
T: Serialize + Eq + Hash,
{
fn serialize<S: Serializer>(&self, serializer: S) -> Result<S::Ok, S::Error> {
self.deref().serialize(serializer)
}
}
#[cfg(feature = "serde")]
impl<'de, T> Deserialize<'de> for AHashSet<T>
where
T: Deserialize<'de> + Eq + Hash,
{
fn deserialize<D: Deserializer<'de>>(deserializer: D) -> Result<Self, D::Error> {
let hash_set = HashSet::deserialize(deserializer);
hash_set.map(|hash_set| Self(hash_set))
}
fn deserialize_in_place<D: Deserializer<'de>>(deserializer: D, place: &mut Self) -> Result<(), D::Error> {
HashSet::deserialize_in_place(deserializer, place)
}
}
#[cfg(all(test, feature = "serde"))]
mod test {
use super::*;
#[test]
fn test_serde() {
let mut set = AHashSet::new();
set.insert("for".to_string());
set.insert("bar".to_string());
let mut serialization = serde_json::to_string(&set).unwrap();
let mut deserialization: AHashSet<String> = serde_json::from_str(&serialization).unwrap();
assert_eq!(deserialization, set);
set.insert("baz".to_string());
serialization = serde_json::to_string(&set).unwrap();
let mut deserializer = serde_json::Deserializer::from_str(&serialization);
AHashSet::deserialize_in_place(&mut deserializer, &mut deserialization).unwrap();
assert_eq!(deserialization, set);
}
}
+358
View File
@@ -0,0 +1,358 @@
//! AHash is a high performance keyed hash function.
//!
//! It quickly provides a high quality hash where the result is not predictable without knowing the Key.
//! AHash works with `HashMap` to hash keys, but without allowing for the possibility that an malicious user can
//! induce a collision.
//!
//! # How aHash works
//!
//! When it is available aHash uses the hardware AES instructions to provide a keyed hash function.
//! When it is not, aHash falls back on a slightly slower alternative algorithm.
//!
//! Because aHash does not have a fixed standard for its output, it is able to improve over time.
//! But this also means that different computers or computers using different versions of ahash may observe different
//! hash values for the same input.
#![cfg_attr(
all(
feature = "std",
any(feature = "compile-time-rng", feature = "runtime-rng", feature = "no-rng")
),
doc = r##"
# Basic Usage
AHash provides an implementation of the [Hasher] trait.
To construct a HashMap using aHash as its hasher do the following:
```
use ahash::{AHasher, RandomState};
use std::collections::HashMap;
let mut map: HashMap<i32, i32, RandomState> = HashMap::default();
map.insert(12, 34);
```
### Randomness
The above requires a source of randomness to generate keys for the hashmap. By default this obtained from the OS.
It is also possible to have randomness supplied via the `compile-time-rng` flag, or manually.
### If randomness is not available
[AHasher::default()] can be used to hash using fixed keys. This works with
[BuildHasherDefault](std::hash::BuildHasherDefault). For example:
```
use std::hash::BuildHasherDefault;
use std::collections::HashMap;
use ahash::AHasher;
let mut m: HashMap<_, _, BuildHasherDefault<AHasher>> = HashMap::default();
# m.insert(12, 34);
```
It is also possible to instantiate [RandomState] directly:
```
use ahash::HashMap;
use ahash::RandomState;
let mut m = HashMap::with_hasher(RandomState::with_seed(42));
# m.insert(1, 2);
```
Or for uses besides a hashhmap:
```
use std::hash::BuildHasher;
use ahash::RandomState;
let hash_builder = RandomState::with_seed(42);
let hash = hash_builder.hash_one("Some Data");
```
There are several constructors for [RandomState] with different ways to supply seeds.
# Convenience wrappers
For convenience, both new-type wrappers and type aliases are provided.
The new type wrappers are called called `AHashMap` and `AHashSet`.
```
use ahash::AHashMap;
let mut map: AHashMap<i32, i32> = AHashMap::new();
map.insert(12, 34);
```
This avoids the need to type "RandomState". (For convenience `From`, `Into`, and `Deref` are provided).
# Aliases
For even less typing and better interop with existing libraries (such as rayon) which require a `std::collection::HashMap` ,
the type aliases [HashMap], [HashSet] are provided.
```
use ahash::{HashMap, HashMapExt};
let mut map: HashMap<i32, i32> = HashMap::new();
map.insert(12, 34);
```
Note the import of [HashMapExt]. This is needed for the constructor.
"##
)]
#![deny(clippy::correctness, clippy::complexity, clippy::perf)]
#![allow(clippy::pedantic, clippy::cast_lossless, clippy::unreadable_literal)]
#![cfg_attr(all(not(test), not(feature = "std")), no_std)]
#![cfg_attr(specialize, feature(min_specialization))]
#![cfg_attr(feature = "nightly-arm-aes", feature(stdarch_arm_neon_intrinsics))]
#[macro_use]
mod convert;
mod fallback_hash;
cfg_if::cfg_if! {
if #[cfg(any(
all(any(target_arch = "x86", target_arch = "x86_64"), target_feature = "aes", not(miri)),
all(feature = "nightly-arm-aes", target_arch = "aarch64", target_feature = "aes", not(miri)),
all(feature = "nightly-arm-aes", target_arch = "arm", target_feature = "aes", not(miri)),
))] {
mod aes_hash;
pub use crate::aes_hash::AHasher;
} else {
pub use crate::fallback_hash::AHasher;
}
}
cfg_if::cfg_if! {
if #[cfg(feature = "std")] {
mod hash_map;
mod hash_set;
pub use crate::hash_map::AHashMap;
pub use crate::hash_set::AHashSet;
/// [Hasher]: std::hash::Hasher
/// [HashMap]: std::collections::HashMap
/// Type alias for [HashMap]<K, V, ahash::RandomState>
pub type HashMap<K, V> = std::collections::HashMap<K, V, crate::RandomState>;
/// Type alias for [HashSet]<K, ahash::RandomState>
pub type HashSet<K> = std::collections::HashSet<K, crate::RandomState>;
}
}
#[cfg(test)]
mod hash_quality_test;
mod operations;
pub mod random_state;
mod specialize;
pub use crate::random_state::RandomState;
use core::hash::BuildHasher;
#[cfg(feature = "std")]
/// A convenience trait that can be used together with the type aliases defined to
/// get access to the `new()` and `with_capacity()` methods for the HashMap type alias.
pub trait HashMapExt {
/// Constructs a new HashMap
fn new() -> Self;
/// Constructs a new HashMap with a given initial capacity
fn with_capacity(capacity: usize) -> Self;
}
#[cfg(feature = "std")]
/// A convenience trait that can be used together with the type aliases defined to
/// get access to the `new()` and `with_capacity()` methods for the HashSet type aliases.
pub trait HashSetExt {
/// Constructs a new HashSet
fn new() -> Self;
/// Constructs a new HashSet with a given initial capacity
fn with_capacity(capacity: usize) -> Self;
}
#[cfg(feature = "std")]
impl<K, V, S> HashMapExt for std::collections::HashMap<K, V, S>
where
S: BuildHasher + Default,
{
fn new() -> Self {
std::collections::HashMap::with_hasher(S::default())
}
fn with_capacity(capacity: usize) -> Self {
std::collections::HashMap::with_capacity_and_hasher(capacity, S::default())
}
}
#[cfg(feature = "std")]
impl<K, S> HashSetExt for std::collections::HashSet<K, S>
where
S: BuildHasher + Default,
{
fn new() -> Self {
std::collections::HashSet::with_hasher(S::default())
}
fn with_capacity(capacity: usize) -> Self {
std::collections::HashSet::with_capacity_and_hasher(capacity, S::default())
}
}
/// Provides a default [Hasher] with fixed keys.
/// This is typically used in conjunction with [BuildHasherDefault] to create
/// [AHasher]s in order to hash the keys of the map.
///
/// Generally it is preferable to use [RandomState] instead, so that different
/// hashmaps will have different keys. However if fixed keys are desirable this
/// may be used instead.
///
/// # Example
/// ```
/// use std::hash::BuildHasherDefault;
/// use ahash::{AHasher, RandomState};
/// use std::collections::HashMap;
///
/// let mut map: HashMap<i32, i32, BuildHasherDefault<AHasher>> = HashMap::default();
/// map.insert(12, 34);
/// ```
///
/// [BuildHasherDefault]: std::hash::BuildHasherDefault
/// [Hasher]: std::hash::Hasher
/// [HashMap]: std::collections::HashMap
impl Default for AHasher {
/// Constructs a new [AHasher] with fixed keys.
/// If `std` is enabled these will be generated upon first invocation.
/// Otherwise if the `compile-time-rng`feature is enabled these will be generated at compile time.
/// If neither of these features are available, hardcoded constants will be used.
///
/// Because the values are fixed, different hashers will all hash elements the same way.
/// This could make hash values predictable, if DOS attacks are a concern. If this behaviour is
/// not required, it may be preferable to use [RandomState] instead.
///
/// # Examples
///
/// ```
/// use ahash::AHasher;
/// use std::hash::Hasher;
///
/// let mut hasher_1 = AHasher::default();
/// let mut hasher_2 = AHasher::default();
///
/// hasher_1.write_u32(1234);
/// hasher_2.write_u32(1234);
///
/// assert_eq!(hasher_1.finish(), hasher_2.finish());
/// ```
#[inline]
fn default() -> AHasher {
RandomState::with_fixed_keys().build_hasher()
}
}
// #[inline(never)]
// #[doc(hidden)]
// pub fn hash_test(input: &[u8]) -> u64 {
// let a = RandomState::with_seeds(11, 22, 33, 44);
// <[u8]>::get_hash(input, &a)
// }
#[cfg(feature = "std")]
#[cfg(test)]
mod test {
use crate::convert::Convert;
use crate::specialize::CallHasher;
use crate::*;
use core::hash::Hash;
use core::hash::Hasher;
use std::collections::HashMap;
#[test]
fn test_ahash_alias_map_construction() {
let mut map = super::HashMap::with_capacity(1234);
map.insert(1, "test");
}
#[test]
fn test_ahash_alias_set_construction() {
let mut set = super::HashSet::with_capacity(1234);
set.insert(1);
}
#[test]
fn test_default_builder() {
use core::hash::BuildHasherDefault;
let mut map = HashMap::<u32, u64, BuildHasherDefault<AHasher>>::default();
map.insert(1, 3);
}
#[test]
fn test_builder() {
let mut map = HashMap::<u32, u64, RandomState>::default();
map.insert(1, 3);
}
#[test]
fn test_conversion() {
let input: &[u8] = b"dddddddd";
let bytes: u64 = as_array!(input, 8).convert();
assert_eq!(bytes, 0x6464646464646464);
}
#[test]
fn test_non_zero() {
let mut hasher1 = AHasher::new_with_keys(0, 0);
let mut hasher2 = AHasher::new_with_keys(0, 0);
"foo".hash(&mut hasher1);
"bar".hash(&mut hasher2);
assert_ne!(hasher1.finish(), 0);
assert_ne!(hasher2.finish(), 0);
assert_ne!(hasher1.finish(), hasher2.finish());
let mut hasher1 = AHasher::new_with_keys(0, 0);
let mut hasher2 = AHasher::new_with_keys(0, 0);
3_u64.hash(&mut hasher1);
4_u64.hash(&mut hasher2);
assert_ne!(hasher1.finish(), 0);
assert_ne!(hasher2.finish(), 0);
assert_ne!(hasher1.finish(), hasher2.finish());
}
#[test]
fn test_non_zero_specialized() {
let hasher_build = RandomState::with_seeds(0, 0, 0, 0);
let h1 = str::get_hash("foo", &hasher_build);
let h2 = str::get_hash("bar", &hasher_build);
assert_ne!(h1, 0);
assert_ne!(h2, 0);
assert_ne!(h1, h2);
let h1 = u64::get_hash(&3_u64, &hasher_build);
let h2 = u64::get_hash(&4_u64, &hasher_build);
assert_ne!(h1, 0);
assert_ne!(h2, 0);
assert_ne!(h1, h2);
}
#[test]
fn test_ahasher_construction() {
let _ = AHasher::new_with_keys(1234, 5678);
}
#[test]
fn test_specialize_reference_hash() {
let hasher_build = RandomState::with_seeds(0, 0, 0, 0);
let h1 = hasher_build.hash_one(1u64);
let h2 = hasher_build.hash_one(&1u64);
assert_eq!(h1, h2);
let h1 = u64::get_hash(&1_u64, &hasher_build);
let h2 = <&u64>::get_hash(&&1_u64, &hasher_build);
assert_eq!(h1, h2);
let h1 = hasher_build.hash_one(1u128);
let h2 = hasher_build.hash_one(&1u128);
assert_eq!(h1, h2);
}
}
+374
View File
@@ -0,0 +1,374 @@
use crate::convert::*;
#[allow(unused)]
use zerocopy::transmute;
///This constant comes from Kunth's prng (Empirically it works better than those from splitmix32).
pub(crate) const MULTIPLE: u64 = 6364136223846793005;
/// This is a constant with a lot of special properties found by automated search.
/// See the unit tests below. (Below are alternative values)
#[cfg(all(target_feature = "ssse3", not(miri)))]
const SHUFFLE_MASK: u128 = 0x020a0700_0c01030e_050f0d08_06090b04_u128;
//const SHUFFLE_MASK: u128 = 0x000d0702_0a040301_05080f0c_0e0b0609_u128;
//const SHUFFLE_MASK: u128 = 0x040A0700_030E0106_0D050F08_020B0C09_u128;
#[inline(always)]
#[cfg(folded_multiply)]
pub(crate) const fn folded_multiply(s: u64, by: u64) -> u64 {
let result = (s as u128).wrapping_mul(by as u128);
((result & 0xffff_ffff_ffff_ffff) as u64) ^ ((result >> 64) as u64)
}
#[inline(always)]
#[cfg(not(folded_multiply))]
pub(crate) const fn folded_multiply(s: u64, by: u64) -> u64 {
let b1 = s.wrapping_mul(by.swap_bytes());
let b2 = s.swap_bytes().wrapping_mul(!by);
b1 ^ b2.swap_bytes()
}
/// Given a small (less than 8 byte slice) returns the same data stored in two u32s.
/// (order of and non-duplication of bytes is NOT guaranteed)
#[inline(always)]
pub(crate) fn read_small(data: &[u8]) -> [u64; 2] {
debug_assert!(data.len() <= 8);
if data.len() >= 2 {
if data.len() >= 4 {
//len 4-8
[data.read_u32().0 as u64, data.read_last_u32() as u64]
} else {
//len 2-3
[data.read_u16().0 as u64, data[data.len() - 1] as u64]
}
} else {
if data.len() > 0 {
[data[0] as u64, data[0] as u64]
} else {
[0, 0]
}
}
}
#[inline(always)]
pub(crate) fn shuffle(a: u128) -> u128 {
#[cfg(all(target_feature = "ssse3", not(miri)))]
{
#[cfg(target_arch = "x86")]
use core::arch::x86::*;
#[cfg(target_arch = "x86_64")]
use core::arch::x86_64::*;
unsafe { transmute!(_mm_shuffle_epi8(transmute!(a), transmute!(SHUFFLE_MASK))) }
}
#[cfg(not(all(target_feature = "ssse3", not(miri))))]
{
a.swap_bytes()
}
}
#[allow(unused)] //not used by fallback
#[inline(always)]
pub(crate) fn add_and_shuffle(a: u128, b: u128) -> u128 {
let sum = add_by_64s(a.convert(), b.convert());
shuffle(sum.convert())
}
#[allow(unused)] //not used by fallback
#[inline(always)]
pub(crate) fn shuffle_and_add(base: u128, to_add: u128) -> u128 {
let shuffled: [u64; 2] = shuffle(base).convert();
add_by_64s(shuffled, to_add.convert()).convert()
}
#[cfg(all(any(target_arch = "x86", target_arch = "x86_64"), target_feature = "sse2", not(miri)))]
#[inline(always)]
pub(crate) fn add_by_64s(a: [u64; 2], b: [u64; 2]) -> [u64; 2] {
unsafe {
#[cfg(target_arch = "x86")]
use core::arch::x86::*;
#[cfg(target_arch = "x86_64")]
use core::arch::x86_64::*;
transmute!(_mm_add_epi64(transmute!(a), transmute!(b)))
}
}
#[cfg(not(all(any(target_arch = "x86", target_arch = "x86_64"), target_feature = "sse2", not(miri))))]
#[inline(always)]
pub(crate) fn add_by_64s(a: [u64; 2], b: [u64; 2]) -> [u64; 2] {
[a[0].wrapping_add(b[0]), a[1].wrapping_add(b[1])]
}
#[cfg(all(any(target_arch = "x86", target_arch = "x86_64"), target_feature = "aes", not(miri)))]
#[allow(unused)]
#[inline(always)]
pub(crate) fn aesenc(value: u128, xor: u128) -> u128 {
#[cfg(target_arch = "x86")]
use core::arch::x86::*;
#[cfg(target_arch = "x86_64")]
use core::arch::x86_64::*;
unsafe {
let value = transmute!(value);
transmute!(_mm_aesenc_si128(value, transmute!(xor)))
}
}
#[cfg(any(
all(feature = "nightly-arm-aes", target_arch = "aarch64", target_feature = "aes", not(miri)),
all(feature = "nightly-arm-aes", target_arch = "arm", target_feature = "aes", not(miri)),
))]
#[allow(unused)]
#[inline(always)]
pub(crate) fn aesenc(value: u128, xor: u128) -> u128 {
#[cfg(target_arch = "aarch64")]
use core::arch::aarch64::*;
#[cfg(target_arch = "arm")]
use core::arch::arm::*;
let res = unsafe { vaesmcq_u8(vaeseq_u8(transmute!(value), transmute!(0u128))) };
let value: u128 = transmute!(res);
xor ^ value
}
#[cfg(all(any(target_arch = "x86", target_arch = "x86_64"), target_feature = "aes", not(miri)))]
#[allow(unused)]
#[inline(always)]
pub(crate) fn aesdec(value: u128, xor: u128) -> u128 {
#[cfg(target_arch = "x86")]
use core::arch::x86::*;
#[cfg(target_arch = "x86_64")]
use core::arch::x86_64::*;
unsafe {
let value = transmute!(value);
transmute!(_mm_aesdec_si128(value, transmute!(xor)))
}
}
#[cfg(any(
all(feature = "nightly-arm-aes", target_arch = "aarch64", target_feature = "aes", not(miri)),
all(feature = "nightly-arm-aes", target_arch = "arm", target_feature = "aes", not(miri)),
))]
#[allow(unused)]
#[inline(always)]
pub(crate) fn aesdec(value: u128, xor: u128) -> u128 {
#[cfg(target_arch = "aarch64")]
use core::arch::aarch64::*;
#[cfg(target_arch = "arm")]
use core::arch::arm::*;
let res = unsafe { vaesimcq_u8(vaesdq_u8(transmute!(value), transmute!(0u128))) };
let value: u128 = transmute!(res);
xor ^ value
}
#[allow(unused)]
#[inline(always)]
pub(crate) fn add_in_length(enc: &mut u128, len: u64) {
#[cfg(all(target_arch = "x86_64", target_feature = "sse2", not(miri)))]
{
#[cfg(target_arch = "x86_64")]
use core::arch::x86_64::*;
unsafe {
let enc = enc as *mut u128;
let len = _mm_cvtsi64_si128(len as i64);
let data = _mm_loadu_si128(enc.cast());
let sum = _mm_add_epi64(data, len);
_mm_storeu_si128(enc.cast(), sum);
}
}
#[cfg(not(all(target_arch = "x86_64", target_feature = "sse2", not(miri))))]
{
let mut t: [u64; 2] = enc.convert();
t[0] = t[0].wrapping_add(len);
*enc = t.convert();
}
}
#[cfg(test)]
mod test {
use super::*;
// This is code to search for the shuffle constant
//
//thread_local! { static MASK: Cell<u128> = Cell::new(0); }
//
// fn shuffle(a: u128) -> u128 {
// use std::intrinsics::transmute;
// #[cfg(target_arch = "x86")]
// use core::arch::x86::*;
// #[cfg(target_arch = "x86_64")]
// use core::arch::x86_64::*;
// MASK.with(|mask| {
// unsafe { transmute!(_mm_shuffle_epi8(transmute!(a), transmute!(mask.get()))) }
// })
// }
//
// #[test]
// fn find_shuffle() {
// use rand::prelude::*;
// use SliceRandom;
// use std::panic;
// use std::io::Write;
//
// let mut value: [u8; 16] = [0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12 ,13, 14, 15];
// let mut rand = thread_rng();
// let mut successful_list = HashMap::new();
// for _attempt in 0..10000000 {
// rand.shuffle(&mut value);
// let test_val = value.convert();
// MASK.with(|mask| {
// mask.set(test_val);
// });
// if let Ok(successful) = panic::catch_unwind(|| {
// test_shuffle_does_not_collide_with_aes();
// test_shuffle_moves_high_bits();
// test_shuffle_moves_every_value();
// //test_shuffle_does_not_loop();
// value
// }) {
// let successful: u128 = successful.convert();
// successful_list.insert(successful, iters_before_loop());
// }
// }
// let write_file = File::create("/tmp/output").unwrap();
// let mut writer = BufWriter::new(&write_file);
//
// for success in successful_list {
// writeln!(writer, "Found successful: {:x?} - {:?}", success.0, success.1);
// }
// }
//
// fn iters_before_loop() -> u32 {
// let numbered = 0x00112233_44556677_8899AABB_CCDDEEFF;
// let mut shuffled = shuffle(numbered);
// let mut count = 0;
// loop {
// // println!("{:>16x}", shuffled);
// if numbered == shuffled {
// break;
// }
// count += 1;
// shuffled = shuffle(shuffled);
// }
// count
// }
#[cfg(all(
any(target_arch = "x86", target_arch = "x86_64"),
target_feature = "ssse3",
target_feature = "aes",
not(miri)
))]
#[test]
fn test_shuffle_does_not_collide_with_aes() {
let mut value: [u8; 16] = [0; 16];
let zero_mask_enc = aesenc(0, 0);
let zero_mask_dec = aesdec(0, 0);
for index in 0..16 {
value[index] = 1;
let excluded_positions_enc: [u8; 16] = aesenc(value.convert(), zero_mask_enc).convert();
let excluded_positions_dec: [u8; 16] = aesdec(value.convert(), zero_mask_dec).convert();
let actual_location: [u8; 16] = shuffle(value.convert()).convert();
for pos in 0..16 {
if actual_location[pos] != 0 {
assert_eq!(
0, excluded_positions_enc[pos],
"Forward Overlap between {:?} and {:?} at {}",
excluded_positions_enc, actual_location, index
);
assert_eq!(
0, excluded_positions_dec[pos],
"Reverse Overlap between {:?} and {:?} at {}",
excluded_positions_dec, actual_location, index
);
}
}
value[index] = 0;
}
}
#[test]
fn test_shuffle_contains_each_value() {
let value: [u8; 16] = 0x00010203_04050607_08090A0B_0C0D0E0F_u128.convert();
let shuffled: [u8; 16] = shuffle(value.convert()).convert();
for index in 0..16_u8 {
assert!(shuffled.contains(&index), "Value is missing {}", index);
}
}
#[test]
fn test_shuffle_moves_every_value() {
let mut value: [u8; 16] = [0; 16];
for index in 0..16 {
value[index] = 1;
let shuffled: [u8; 16] = shuffle(value.convert()).convert();
assert_eq!(0, shuffled[index], "Value is not moved {}", index);
value[index] = 0;
}
}
#[test]
fn test_shuffle_moves_high_bits() {
assert!(
shuffle(1) > (1_u128 << 80),
"Low bits must be moved to other half {:?} -> {:?}",
0,
shuffle(1)
);
assert!(
shuffle(1_u128 << 58) >= (1_u128 << 64),
"High bits must be moved to other half {:?} -> {:?}",
7,
shuffle(1_u128 << 58)
);
assert!(
shuffle(1_u128 << 58) < (1_u128 << 112),
"High bits must not remain high {:?} -> {:?}",
7,
shuffle(1_u128 << 58)
);
assert!(
shuffle(1_u128 << 64) < (1_u128 << 64),
"Low bits must be moved to other half {:?} -> {:?}",
8,
shuffle(1_u128 << 64)
);
assert!(
shuffle(1_u128 << 64) >= (1_u128 << 16),
"Low bits must not remain low {:?} -> {:?}",
8,
shuffle(1_u128 << 64)
);
assert!(
shuffle(1_u128 << 120) < (1_u128 << 50),
"High bits must be moved to low half {:?} -> {:?}",
15,
shuffle(1_u128 << 120)
);
}
#[cfg(all(
any(target_arch = "x86", target_arch = "x86_64"),
target_feature = "ssse3",
not(miri)
))]
#[test]
fn test_shuffle_does_not_loop() {
let numbered = 0x00112233_44556677_8899AABB_CCDDEEFF;
let mut shuffled = shuffle(numbered);
for count in 0..100 {
// println!("{:>16x}", shuffled);
assert_ne!(numbered, shuffled, "Equal after {} vs {:x}", count, shuffled);
shuffled = shuffle(shuffled);
}
}
#[test]
fn test_add_length() {
let enc : [u64; 2] = [50, u64::MAX];
let mut enc : u128 = enc.convert();
add_in_length(&mut enc, u64::MAX);
let enc : [u64; 2] = enc.convert();
assert_eq!(enc[1], u64::MAX);
assert_eq!(enc[0], 49);
}
}
+523
View File
@@ -0,0 +1,523 @@
use core::hash::Hash;
cfg_if::cfg_if! {
if #[cfg(any(
all(any(target_arch = "x86", target_arch = "x86_64"), target_feature = "aes", not(miri)),
all(feature = "nightly-arm-aes", target_arch = "aarch64", target_feature = "aes", not(miri)),
all(feature = "nightly-arm-aes", target_arch = "arm", target_feature = "aes", not(miri)),
))] {
use crate::aes_hash::*;
} else {
use crate::fallback_hash::*;
}
}
cfg_if::cfg_if! {
if #[cfg(feature = "std")] {
extern crate std as alloc;
} else {
extern crate alloc;
}
}
#[cfg(feature = "atomic-polyfill")]
use portable_atomic as atomic;
#[cfg(not(feature = "atomic-polyfill"))]
use core::sync::atomic;
use alloc::boxed::Box;
use atomic::{AtomicUsize, Ordering};
use core::any::{Any, TypeId};
use core::fmt;
use core::hash::BuildHasher;
use core::hash::Hasher;
pub(crate) const PI: [u64; 4] = [
0x243f_6a88_85a3_08d3,
0x1319_8a2e_0370_7344,
0xa409_3822_299f_31d0,
0x082e_fa98_ec4e_6c89,
];
pub(crate) const PI2: [u64; 4] = [
0x4528_21e6_38d0_1377,
0xbe54_66cf_34e9_0c6c,
0xc0ac_29b7_c97c_50dd,
0x3f84_d5b5_b547_0917,
];
cfg_if::cfg_if! {
if #[cfg(all(feature = "compile-time-rng", any(test, fuzzing)))] {
#[inline]
fn get_fixed_seeds() -> &'static [[u64; 4]; 2] {
use const_random::const_random;
const RAND: [[u64; 4]; 2] = [
[
const_random!(u64),
const_random!(u64),
const_random!(u64),
const_random!(u64),
], [
const_random!(u64),
const_random!(u64),
const_random!(u64),
const_random!(u64),
]
];
&RAND
}
} else if #[cfg(all(feature = "runtime-rng", not(fuzzing)))] {
#[inline]
fn get_fixed_seeds() -> &'static [[u64; 4]; 2] {
use crate::convert::Convert;
static SEEDS: OnceBox<[[u64; 4]; 2]> = OnceBox::new();
SEEDS.get_or_init(|| {
let mut result: [u8; 64] = [0; 64];
getrandom::fill(&mut result).expect("getrandom::fill() failed.");
Box::new(result.convert())
})
}
} else if #[cfg(feature = "compile-time-rng")] {
#[inline]
fn get_fixed_seeds() -> &'static [[u64; 4]; 2] {
use const_random::const_random;
const RAND: [[u64; 4]; 2] = [
[
const_random!(u64),
const_random!(u64),
const_random!(u64),
const_random!(u64),
], [
const_random!(u64),
const_random!(u64),
const_random!(u64),
const_random!(u64),
]
];
&RAND
}
} else {
#[inline]
fn get_fixed_seeds() -> &'static [[u64; 4]; 2] {
&[PI, PI2]
}
}
}
cfg_if::cfg_if! {
if #[cfg(not(all(target_arch = "arm", target_os = "none")))] {
use once_cell::race::OnceBox;
static RAND_SOURCE: OnceBox<Box<dyn RandomSource + Send + Sync>> = OnceBox::new();
}
}
/// A supplier of Randomness used for different hashers.
/// See [set_random_source].
///
/// If [set_random_source] aHash will default to the best available source of randomness.
/// In order this is:
/// 1. OS provided random number generator (available if the `runtime-rng` flag is enabled which it is by default) - This should be very strong.
/// 2. Strong compile time random numbers used to permute a static "counter". (available if `compile-time-rng` is enabled.
/// __Enabling this is recommended if `runtime-rng` is not possible__)
/// 3. A static counter that adds the memory address of each [RandomState] created permuted with fixed constants.
/// (Similar to above but with fixed keys) - This is the weakest option. The strength of this heavily depends on whether or not ASLR is enabled.
/// (Rust enables ASLR by default)
pub trait RandomSource {
fn gen_hasher_seed(&self) -> usize;
}
struct DefaultRandomSource {
counter: AtomicUsize,
}
impl DefaultRandomSource {
fn new() -> DefaultRandomSource {
DefaultRandomSource {
counter: AtomicUsize::new(&PI as *const _ as usize),
}
}
#[cfg(all(target_arch = "arm", target_os = "none"))]
const fn default() -> DefaultRandomSource {
DefaultRandomSource {
counter: AtomicUsize::new(PI[3] as usize),
}
}
}
impl RandomSource for DefaultRandomSource {
cfg_if::cfg_if! {
if #[cfg(all(target_arch = "arm", target_os = "none"))] {
fn gen_hasher_seed(&self) -> usize {
let stack = self as *const _ as usize;
let previous = self.counter.load(Ordering::Relaxed);
let new = previous.wrapping_add(stack);
self.counter.store(new, Ordering::Relaxed);
new
}
} else {
fn gen_hasher_seed(&self) -> usize {
let stack = self as *const _ as usize;
self.counter.fetch_add(stack, Ordering::Relaxed)
}
}
}
}
cfg_if::cfg_if! {
if #[cfg(all(target_arch = "arm", target_os = "none"))] {
#[inline]
fn get_src() -> &'static dyn RandomSource {
static RAND_SOURCE: DefaultRandomSource = DefaultRandomSource::default();
&RAND_SOURCE
}
} else {
/// Provides an optional way to manually supply a source of randomness for Hasher keys.
///
/// The provided [RandomSource] will be used to be used as a source of randomness by [RandomState] to generate new states.
/// If this method is not invoked the standard source of randomness is used as described in the Readme.
///
/// The source of randomness can only be set once, and must be set before the first RandomState is created.
/// If the source has already been specified `Err` is returned with a `bool` indicating if the set failed because
/// method was previously invoked (true) or if the default source is already being used (false).
#[cfg(not(all(target_arch = "arm", target_os = "none")))]
pub fn set_random_source(source: impl RandomSource + Send + Sync + 'static) -> Result<(), bool> {
RAND_SOURCE.set(Box::new(Box::new(source))).map_err(|s| s.as_ref().type_id() != TypeId::of::<&DefaultRandomSource>())
}
#[inline]
fn get_src() -> &'static dyn RandomSource {
RAND_SOURCE.get_or_init(|| Box::new(Box::new(DefaultRandomSource::new()))).as_ref()
}
}
}
/// Provides a [Hasher] factory. This is typically used (e.g. by [HashMap]) to create
/// [AHasher]s in order to hash the keys of the map. See `build_hasher` below.
///
/// [build_hasher]: ahash::
/// [Hasher]: std::hash::Hasher
/// [BuildHasher]: std::hash::BuildHasher
/// [HashMap]: std::collections::HashMap
///
/// There are multiple constructors each is documented in more detail below:
///
/// | Constructor | Dynamically random? | Seed |
/// |---------------|---------------------|------|
/// |`new` | Each instance unique|_[RandomSource]_|
/// |`generate_with`| Each instance unique|`u64` x 4 + [RandomSource]|
/// |`with_seed` | Fixed per process |`u64` + static random number|
/// |`with_seeds` | Fixed |`u64` x 4|
///
#[derive(Clone)]
pub struct RandomState {
pub(crate) k0: u64,
pub(crate) k1: u64,
pub(crate) k2: u64,
pub(crate) k3: u64,
}
impl fmt::Debug for RandomState {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
f.pad("RandomState { .. }")
}
}
impl RandomState {
/// Create a new `RandomState` `BuildHasher` using random keys.
///
/// Each instance will have a unique set of keys derived from [RandomSource].
///
#[inline]
pub fn new() -> RandomState {
let src = get_src();
let fixed = get_fixed_seeds();
Self::from_keys(&fixed[0], &fixed[1], src.gen_hasher_seed())
}
/// Create a new `RandomState` `BuildHasher` based on the provided seeds, but in such a way
/// that each time it is called the resulting state will be different and of high quality.
/// This allows fixed constant or poor quality seeds to be provided without the problem of different
/// `BuildHasher`s being identical or weak.
///
/// This is done via permuting the provided values with the value of a static counter and memory address.
/// (This makes this method somewhat more expensive than `with_seeds` below which does not do this).
///
/// The provided values (k0-k3) do not need to be of high quality but they should not all be the same value.
#[inline]
pub fn generate_with(k0: u64, k1: u64, k2: u64, k3: u64) -> RandomState {
let src = get_src();
let fixed = get_fixed_seeds();
RandomState::from_keys(&fixed[0], &[k0, k1, k2, k3], src.gen_hasher_seed())
}
fn from_keys(a: &[u64; 4], b: &[u64; 4], c: usize) -> RandomState {
let &[k0, k1, k2, k3] = a;
let mut hasher = AHasher::from_random_state(&RandomState { k0, k1, k2, k3 });
hasher.write_usize(c);
let mix = |l: u64, r: u64| {
let mut h = hasher.clone();
h.write_u64(l);
h.write_u64(r);
h.finish()
};
RandomState {
k0: mix(b[0], b[2]),
k1: mix(b[1], b[3]),
k2: mix(b[2], b[1]),
k3: mix(b[3], b[0]),
}
}
/// Internal. Used by Default.
#[inline]
pub(crate) fn with_fixed_keys() -> RandomState {
let [k0, k1, k2, k3] = get_fixed_seeds()[0];
RandomState { k0, k1, k2, k3 }
}
/// Build a `RandomState` from a single key. The provided key does not need to be of high quality,
/// but all `RandomState`s created from the same key will produce identical hashers.
/// (In contrast to `generate_with` above)
///
/// This allows for explicitly setting the seed to be used.
///
/// Note: This method does not require the provided seed to be strong.
#[inline]
pub fn with_seed(key: usize) -> RandomState {
let fixed = get_fixed_seeds();
RandomState::from_keys(&fixed[0], &fixed[1], key)
}
/// Allows for explicitly setting the seeds to used.
/// All `RandomState`s created with the same set of keys key will produce identical hashers.
/// (In contrast to `generate_with` above)
///
/// Note: If DOS resistance is desired one of these should be a decent quality random number.
/// If 4 high quality random number are not cheaply available this method is robust against 0s being passed for
/// one or more of the parameters or the same value being passed for more than one parameter.
/// It is recommended to pass numbers in order from highest to lowest quality (if there is any difference).
#[inline]
pub const fn with_seeds(k0: u64, k1: u64, k2: u64, k3: u64) -> RandomState {
RandomState {
k0: k0 ^ PI2[0],
k1: k1 ^ PI2[1],
k2: k2 ^ PI2[2],
k3: k3 ^ PI2[3],
}
}
/// Calculates the hash of a single value. This provides a more convenient (and faster) way to obtain a hash:
/// For example:
#[cfg_attr(
feature = "std",
doc = r##" # Examples
```
use std::hash::BuildHasher;
use ahash::RandomState;
let hash_builder = RandomState::new();
let hash = hash_builder.hash_one("Some Data");
```
"##
)]
/// This is similar to:
#[cfg_attr(
feature = "std",
doc = r##" # Examples
```
use std::hash::{BuildHasher, Hash, Hasher};
use ahash::RandomState;
let hash_builder = RandomState::new();
let mut hasher = hash_builder.build_hasher();
"Some Data".hash(&mut hasher);
let hash = hasher.finish();
```
"##
)]
/// (Note that these two ways to get a hash may not produce the same value for the same data)
///
/// This is intended as a convenience for code which *consumes* hashes, such
/// as the implementation of a hash table or in unit tests that check
/// whether a custom [`Hash`] implementation behaves as expected.
///
/// This must not be used in any code which *creates* hashes, such as in an
/// implementation of [`Hash`]. The way to create a combined hash of
/// multiple values is to call [`Hash::hash`] multiple times using the same
/// [`Hasher`], not to call this method repeatedly and combine the results.
#[inline]
pub fn hash_one<T: Hash>(&self, x: T) -> u64
where
Self: Sized,
{
use crate::specialize::CallHasher;
T::get_hash(&x, self)
}
}
/// Creates an instance of RandomState using keys obtained from the random number generator.
/// Each instance created in this way will have a unique set of keys. (But the resulting instance
/// can be used to create many hashers each or which will have the same keys.)
///
/// This is the same as [RandomState::new()]
///
/// NOTE: For safety this trait impl is only available available if either of the flags `runtime-rng` (on by default) or
/// `compile-time-rng` are enabled. This is to prevent weakly keyed maps from being accidentally created. Instead one of
/// constructors for [RandomState] must be used.
#[cfg(any(feature = "compile-time-rng", feature = "runtime-rng", feature = "no-rng"))]
impl Default for RandomState {
#[inline]
fn default() -> Self {
Self::new()
}
}
impl BuildHasher for RandomState {
type Hasher = AHasher;
/// Constructs a new [AHasher] with keys based on this [RandomState] object.
/// This means that two different [RandomState]s will will generate
/// [AHasher]s that will return different hashcodes, but [Hasher]s created from the same [BuildHasher]
/// will generate the same hashes for the same input data.
///
#[cfg_attr(
feature = "std",
doc = r##" # Examples
```
use ahash::{AHasher, RandomState};
use std::hash::{Hasher, BuildHasher};
let build_hasher = RandomState::new();
let mut hasher_1 = build_hasher.build_hasher();
let mut hasher_2 = build_hasher.build_hasher();
hasher_1.write_u32(1234);
hasher_2.write_u32(1234);
assert_eq!(hasher_1.finish(), hasher_2.finish());
let other_build_hasher = RandomState::new();
let mut different_hasher = other_build_hasher.build_hasher();
different_hasher.write_u32(1234);
assert_ne!(different_hasher.finish(), hasher_1.finish());
```
"##
)]
/// [Hasher]: std::hash::Hasher
/// [BuildHasher]: std::hash::BuildHasher
/// [HashMap]: std::collections::HashMap
#[inline]
fn build_hasher(&self) -> AHasher {
AHasher::from_random_state(self)
}
/// Calculates the hash of a single value. This provides a more convenient (and faster) way to obtain a hash:
/// For example:
#[cfg_attr(
feature = "std",
doc = r##" # Examples
```
use std::hash::BuildHasher;
use ahash::RandomState;
let hash_builder = RandomState::new();
let hash = hash_builder.hash_one("Some Data");
```
"##
)]
/// This is similar to:
#[cfg_attr(
feature = "std",
doc = r##" # Examples
```
use std::hash::{BuildHasher, Hash, Hasher};
use ahash::RandomState;
let hash_builder = RandomState::new();
let mut hasher = hash_builder.build_hasher();
"Some Data".hash(&mut hasher);
let hash = hasher.finish();
```
"##
)]
/// (Note that these two ways to get a hash may not produce the same value for the same data)
///
/// This is intended as a convenience for code which *consumes* hashes, such
/// as the implementation of a hash table or in unit tests that check
/// whether a custom [`Hash`] implementation behaves as expected.
///
/// This must not be used in any code which *creates* hashes, such as in an
/// implementation of [`Hash`]. The way to create a combined hash of
/// multiple values is to call [`Hash::hash`] multiple times using the same
/// [`Hasher`], not to call this method repeatedly and combine the results.
#[cfg(specialize)]
#[inline]
fn hash_one<T: Hash>(&self, x: T) -> u64 {
RandomState::hash_one(self, x)
}
}
#[cfg(specialize)]
impl RandomState {
#[inline]
pub(crate) fn hash_as_u64<T: Hash + ?Sized>(&self, value: &T) -> u64 {
let mut hasher = AHasherU64 {
buffer: self.k1,
pad: self.k0,
};
value.hash(&mut hasher);
hasher.finish()
}
#[inline]
pub(crate) fn hash_as_fixed_length<T: Hash + ?Sized>(&self, value: &T) -> u64 {
let mut hasher = AHasherFixed(self.build_hasher());
value.hash(&mut hasher);
hasher.finish()
}
#[inline]
pub(crate) fn hash_as_str<T: Hash + ?Sized>(&self, value: &T) -> u64 {
let mut hasher = AHasherStr(self.build_hasher());
value.hash(&mut hasher);
hasher.finish()
}
}
#[cfg(test)]
mod test {
use super::*;
#[test]
fn test_unique() {
let a = RandomState::generate_with(1, 2, 3, 4);
let b = RandomState::generate_with(1, 2, 3, 4);
assert_ne!(a.build_hasher().finish(), b.build_hasher().finish());
}
#[cfg(all(feature = "runtime-rng", not(all(feature = "compile-time-rng", test))))]
#[test]
fn test_not_pi() {
assert_ne!(PI, get_fixed_seeds()[0]);
}
#[cfg(all(feature = "compile-time-rng", any(not(feature = "runtime-rng"), test)))]
#[test]
fn test_not_pi_const() {
assert_ne!(PI, get_fixed_seeds()[0]);
}
#[cfg(all(not(feature = "runtime-rng"), not(feature = "compile-time-rng")))]
#[test]
fn test_pi() {
assert_eq!(PI, get_fixed_seeds()[0]);
}
#[test]
fn test_with_seeds_const() {
const _CONST_RANDOM_STATE: RandomState = RandomState::with_seeds(17, 19, 21, 23);
}
}
+214
View File
@@ -0,0 +1,214 @@
use crate::RandomState;
use core::hash::BuildHasher;
use core::hash::Hash;
use core::hash::Hasher;
#[cfg(not(feature = "std"))]
extern crate alloc;
#[cfg(feature = "std")]
extern crate std as alloc;
#[cfg(specialize)]
use alloc::string::String;
#[cfg(specialize)]
use alloc::vec::Vec;
/// Provides a way to get an optimized hasher for a given data type.
/// Rather than using a Hasher generically which can hash any value, this provides a way to get a specialized hash
/// for a specific type. So this may be faster for primitive types.
pub(crate) trait CallHasher {
fn get_hash<H: Hash + ?Sized>(value: &H, random_state: &RandomState) -> u64;
}
#[cfg(not(specialize))]
impl<T> CallHasher for T
where
T: Hash + ?Sized,
{
#[inline]
fn get_hash<H: Hash + ?Sized>(value: &H, random_state: &RandomState) -> u64 {
let mut hasher = random_state.build_hasher();
value.hash(&mut hasher);
hasher.finish()
}
}
#[cfg(specialize)]
impl<T> CallHasher for T
where
T: Hash + ?Sized,
{
#[inline]
default fn get_hash<H: Hash + ?Sized>(value: &H, random_state: &RandomState) -> u64 {
let mut hasher = random_state.build_hasher();
value.hash(&mut hasher);
hasher.finish()
}
}
macro_rules! call_hasher_impl_u64 {
($typ:ty) => {
#[cfg(specialize)]
impl CallHasher for $typ {
#[inline]
fn get_hash<H: Hash + ?Sized>(value: &H, random_state: &RandomState) -> u64 {
random_state.hash_as_u64(value)
}
}
};
}
call_hasher_impl_u64!(u8);
call_hasher_impl_u64!(u16);
call_hasher_impl_u64!(u32);
call_hasher_impl_u64!(u64);
call_hasher_impl_u64!(i8);
call_hasher_impl_u64!(i16);
call_hasher_impl_u64!(i32);
call_hasher_impl_u64!(i64);
call_hasher_impl_u64!(&u8);
call_hasher_impl_u64!(&u16);
call_hasher_impl_u64!(&u32);
call_hasher_impl_u64!(&u64);
call_hasher_impl_u64!(&i8);
call_hasher_impl_u64!(&i16);
call_hasher_impl_u64!(&i32);
call_hasher_impl_u64!(&i64);
macro_rules! call_hasher_impl_fixed_length{
($typ:ty) => {
#[cfg(specialize)]
impl CallHasher for $typ {
#[inline]
fn get_hash<H: Hash + ?Sized>(value: &H, random_state: &RandomState) -> u64 {
random_state.hash_as_fixed_length(value)
}
}
};
}
call_hasher_impl_fixed_length!(u128);
call_hasher_impl_fixed_length!(i128);
call_hasher_impl_fixed_length!(usize);
call_hasher_impl_fixed_length!(isize);
call_hasher_impl_fixed_length!(&u128);
call_hasher_impl_fixed_length!(&i128);
call_hasher_impl_fixed_length!(&usize);
call_hasher_impl_fixed_length!(&isize);
#[cfg(specialize)]
impl CallHasher for [u8] {
#[inline]
fn get_hash<H: Hash + ?Sized>(value: &H, random_state: &RandomState) -> u64 {
random_state.hash_as_str(value)
}
}
#[cfg(specialize)]
impl CallHasher for Vec<u8> {
#[inline]
fn get_hash<H: Hash + ?Sized>(value: &H, random_state: &RandomState) -> u64 {
random_state.hash_as_str(value)
}
}
#[cfg(specialize)]
impl CallHasher for str {
#[inline]
fn get_hash<H: Hash + ?Sized>(value: &H, random_state: &RandomState) -> u64 {
random_state.hash_as_str(value)
}
}
#[cfg(all(specialize))]
impl CallHasher for String {
#[inline]
fn get_hash<H: Hash + ?Sized>(value: &H, random_state: &RandomState) -> u64 {
random_state.hash_as_str(value)
}
}
#[cfg(test)]
mod test {
use super::*;
use crate::*;
#[test]
#[cfg(specialize)]
pub fn test_specialized_invoked() {
let build_hasher = RandomState::with_seeds(1, 2, 3, 4);
let shortened = u64::get_hash(&0, &build_hasher);
let mut hasher = AHasher::new_with_keys(1, 2);
0_u64.hash(&mut hasher);
assert_ne!(hasher.finish(), shortened);
}
/// Tests that some non-trivial transformation takes place.
#[test]
pub fn test_input_processed() {
let build_hasher = RandomState::with_seeds(2, 2, 2, 2);
assert_ne!(0, u64::get_hash(&0, &build_hasher));
assert_ne!(1, u64::get_hash(&0, &build_hasher));
assert_ne!(2, u64::get_hash(&0, &build_hasher));
assert_ne!(3, u64::get_hash(&0, &build_hasher));
assert_ne!(4, u64::get_hash(&0, &build_hasher));
assert_ne!(5, u64::get_hash(&0, &build_hasher));
assert_ne!(0, u64::get_hash(&1, &build_hasher));
assert_ne!(1, u64::get_hash(&1, &build_hasher));
assert_ne!(2, u64::get_hash(&1, &build_hasher));
assert_ne!(3, u64::get_hash(&1, &build_hasher));
assert_ne!(4, u64::get_hash(&1, &build_hasher));
assert_ne!(5, u64::get_hash(&1, &build_hasher));
let xored = u64::get_hash(&0, &build_hasher) ^ u64::get_hash(&1, &build_hasher);
assert_ne!(0, xored);
assert_ne!(1, xored);
assert_ne!(2, xored);
assert_ne!(3, xored);
assert_ne!(4, xored);
assert_ne!(5, xored);
}
#[test]
pub fn test_ref_independent() {
let build_hasher = RandomState::with_seeds(1, 2, 3, 4);
assert_eq!(u8::get_hash(&&1, &build_hasher), u8::get_hash(&1, &build_hasher));
assert_eq!(u16::get_hash(&&2, &build_hasher), u16::get_hash(&2, &build_hasher));
assert_eq!(u32::get_hash(&&3, &build_hasher), u32::get_hash(&3, &build_hasher));
assert_eq!(u64::get_hash(&&4, &build_hasher), u64::get_hash(&4, &build_hasher));
assert_eq!(u128::get_hash(&&5, &build_hasher), u128::get_hash(&5, &build_hasher));
assert_eq!(
str::get_hash(&"test", &build_hasher),
str::get_hash("test", &build_hasher)
);
assert_eq!(
str::get_hash(&"test", &build_hasher),
String::get_hash(&"test".to_string(), &build_hasher)
);
#[cfg(specialize)]
assert_eq!(
str::get_hash(&"test", &build_hasher),
<[u8]>::get_hash("test".as_bytes(), &build_hasher)
);
let build_hasher = RandomState::with_seeds(10, 20, 30, 40);
assert_eq!(u8::get_hash(&&&1, &build_hasher), u8::get_hash(&1, &build_hasher));
assert_eq!(u16::get_hash(&&&2, &build_hasher), u16::get_hash(&2, &build_hasher));
assert_eq!(u32::get_hash(&&&3, &build_hasher), u32::get_hash(&3, &build_hasher));
assert_eq!(u64::get_hash(&&&4, &build_hasher), u64::get_hash(&4, &build_hasher));
assert_eq!(u128::get_hash(&&&5, &build_hasher), u128::get_hash(&5, &build_hasher));
assert_eq!(
str::get_hash(&&"test", &build_hasher),
str::get_hash("test", &build_hasher)
);
assert_eq!(
str::get_hash(&&"test", &build_hasher),
String::get_hash(&"test".to_string(), &build_hasher)
);
#[cfg(specialize)]
assert_eq!(
str::get_hash(&&"test", &build_hasher),
<[u8]>::get_hash(&"test".to_string().into_bytes(), &build_hasher)
);
}
}
+199
View File
@@ -0,0 +1,199 @@
#![cfg_attr(specialize, feature(build_hasher_simple_hash_one))]
use ahash::{AHasher, RandomState};
use criterion::*;
use fxhash::FxHasher;
use rand::Rng;
use std::collections::hash_map::DefaultHasher;
use std::hash::{BuildHasherDefault, Hash, Hasher};
// Needs to be in sync with `src/lib.rs`
const AHASH_IMPL: &str = if cfg!(any(
all(
any(target_arch = "x86", target_arch = "x86_64"),
target_feature = "aes",
not(miri),
),
all(feature = "nightly-arm-aes", target_arch = "aarch64", target_feature = "aes", not(miri)),
all(
feature = "nightly-arm-aes",
target_arch = "arm",
target_feature = "aes",
not(miri)
),
)) {
"aeshash"
} else {
"fallbackhash"
};
fn ahash<H: Hash>(b: &H) -> u64 {
let build_hasher = RandomState::with_seeds(1, 2, 3, 4);
build_hasher.hash_one(b)
}
fn fnvhash<H: Hash>(b: &H) -> u64 {
let mut hasher = fnv::FnvHasher::default();
b.hash(&mut hasher);
hasher.finish()
}
fn siphash<H: Hash>(b: &H) -> u64 {
let mut hasher = DefaultHasher::default();
b.hash(&mut hasher);
hasher.finish()
}
fn fxhash<H: Hash>(b: &H) -> u64 {
let mut hasher = FxHasher::default();
b.hash(&mut hasher);
hasher.finish()
}
fn seahash<H: Hash>(b: &H) -> u64 {
let mut hasher = seahash::SeaHasher::default();
b.hash(&mut hasher);
hasher.finish()
}
const STRING_LENGTHS: [u32; 12] = [1, 3, 4, 7, 8, 15, 16, 24, 33, 68, 132, 1024];
fn gen_strings() -> Vec<String> {
STRING_LENGTHS
.iter()
.map(|len| {
let mut string = String::default();
for pos in 1..=*len {
let c = (48 + (pos % 10) as u8) as char;
string.push(c);
}
string
})
.collect()
}
macro_rules! bench_inputs {
($group:ident, $hash:ident) => {
// Number of iterations per batch should be high enough to hide timing overhead.
let size = BatchSize::NumIterations(50_000);
let mut rng = rand::thread_rng();
$group.bench_function("u8", |b| b.iter_batched(|| rng.gen::<u8>(), |v| $hash(&v), size));
$group.bench_function("u16", |b| b.iter_batched(|| rng.gen::<u16>(), |v| $hash(&v), size));
$group.bench_function("u32", |b| b.iter_batched(|| rng.gen::<u32>(), |v| $hash(&v), size));
$group.bench_function("u64", |b| b.iter_batched(|| rng.gen::<u64>(), |v| $hash(&v), size));
$group.bench_function("u128", |b| b.iter_batched(|| rng.gen::<u128>(), |v| $hash(&v), size));
$group.bench_with_input("strings", &gen_strings(), |b, s| b.iter(|| $hash(black_box(s))));
};
}
fn bench_ahash(c: &mut Criterion) {
let mut group = c.benchmark_group(AHASH_IMPL);
bench_inputs!(group, ahash);
}
fn bench_fx(c: &mut Criterion) {
let mut group = c.benchmark_group("fx");
bench_inputs!(group, fxhash);
}
fn bench_fnv(c: &mut Criterion) {
let mut group = c.benchmark_group("fnv");
bench_inputs!(group, fnvhash);
}
fn bench_sea(c: &mut Criterion) {
let mut group = c.benchmark_group("sea");
bench_inputs!(group, seahash);
}
fn bench_sip(c: &mut Criterion) {
let mut group = c.benchmark_group("sip");
bench_inputs!(group, siphash);
}
fn bench_map(c: &mut Criterion) {
#[cfg(feature = "std")]
{
let mut group = c.benchmark_group("map");
group.bench_function("aHash-alias", |b| {
b.iter(|| {
let hm: ahash::HashMap<i32, i32> = (0..1_000_000).map(|i| (i, i)).collect();
let mut sum = 0;
for i in 0..1_000_000 {
if let Some(x) = hm.get(&i) {
sum += x;
}
}
})
});
group.bench_function("aHash-hashBrown", |b| {
b.iter(|| {
let hm: hashbrown::HashMap<i32, i32> = (0..1_000_000).map(|i| (i, i)).collect();
let mut sum = 0;
for i in 0..1_000_000 {
if let Some(x) = hm.get(&i) {
sum += x;
}
}
})
});
group.bench_function("aHash-hashBrown-explicit", |b| {
b.iter(|| {
let hm: hashbrown::HashMap<i32, i32, RandomState> = (0..1_000_000).map(|i| (i, i)).collect();
let mut sum = 0;
for i in 0..1_000_000 {
if let Some(x) = hm.get(&i) {
sum += x;
}
}
})
});
group.bench_function("aHash-wrapper", |b| {
b.iter(|| {
let hm: ahash::AHashMap<i32, i32> = (0..1_000_000).map(|i| (i, i)).collect();
let mut sum = 0;
for i in 0..1_000_000 {
if let Some(x) = hm.get(&i) {
sum += x;
}
}
})
});
group.bench_function("aHash-rand", |b| {
b.iter(|| {
let hm: std::collections::HashMap<i32, i32, RandomState> = (0..1_000_000).map(|i| (i, i)).collect();
let mut sum = 0;
for i in 0..1_000_000 {
if let Some(x) = hm.get(&i) {
sum += x;
}
}
})
});
group.bench_function("aHash-default", |b| {
b.iter(|| {
let hm: std::collections::HashMap<i32, i32, BuildHasherDefault<AHasher>> =
(0..1_000_000).map(|i| (i, i)).collect();
let mut sum = 0;
for i in 0..1_000_000 {
if let Some(x) = hm.get(&i) {
sum += x;
}
}
})
});
}
}
criterion_main!(benches);
criterion_group!(
benches,
bench_ahash,
bench_fx,
bench_fnv,
bench_sea,
bench_sip,
bench_map
);
+310
View File
@@ -0,0 +1,310 @@
#![cfg_attr(specialize, feature(build_hasher_simple_hash_one))]
use std::hash::{BuildHasher, Hash, Hasher};
use ahash::RandomState;
use criterion::*;
use fxhash::FxHasher;
fn gen_word_pairs() -> Vec<String> {
let words: Vec<_> = r#"
a, ability, able, about, above, accept, according, account, across, act, action,
activity, actually, add, address, administration, admit, adult, affect, after,
again, against, age, agency, agent, ago, agree, agreement, ahead, air, all,
allow, almost, alone, along, already, also, although, always, American, among,
amount, analysis, and, animal, another, answer, any, anyone, anything, appear,
apply, approach, area, argue, arm, around, arrive, art, article, artist, as,
ask, assume, at, attack, attention, attorney, audience, author, authority,
available, avoid, away, baby, back, bad, bag, ball, bank, bar, base, be, beat,
beautiful, because, become, bed, before, begin, behavior, behind, believe,
benefit, best, better, between, beyond, big, bill, billion, bit, black, blood,
blue, board, body, book, born, both, box, boy, break, bring, brother, budget,
build, building, business, but, buy, by, call, camera, campaign, can, cancer,
candidate, capital, car, card, care, career, carry, case, catch, cause, cell,
center, central, century, certain, certainly, chair, challenge, chance, change,
character, charge, check, child, choice, choose, church, citizen, city, civil,
claim, class, clear, clearly, close, coach, cold, collection, college, color,
come, commercial, common, community, company, compare, computer, concern,
condition, conference, Congress, consider, consumer, contain, continue, control,
cost, could, country, couple, course, court, cover, create, crime, cultural,
culture, cup, current, customer, cut, dark, data, daughter, day, dead, deal,
death, debate, decade, decide, decision, deep, defense, degree, Democrat,
democratic, describe, design, despite, detail, determine, develop, development,
die, difference, different, difficult, dinner, direction, director, discover,
discuss, discussion, disease, do, doctor, dog, door, down, draw, dream, drive,
drop, drug, during, each, early, east, easy, eat, economic, economy, edge,
education, effect, effort, eight, either, election, else, employee, end, energy,
enjoy, enough, enter, entire, environment, environmental, especially, establish,
even, evening, event, ever, every, everybody, everyone, everything, evidence,
exactly, example, executive, exist, expect, experience, expert, explain, eye,
face, fact, factor, fail, fall, family, far, fast, father, fear, federal, feel,
feeling, few, field, fight, figure, fill, film, final, finally, financial, find,
fine, finger, finish, fire, firm, first, fish, five, floor, fly, focus, follow,
food, foot, for, force, foreign, forget, form, former, forward, four, free,
friend, from, front, full, fund, future, game, garden, gas, general, generation,
get, girl, give, glass, go, goal, good, government, great, green, ground, group,
grow, growth, guess, gun, guy, hair, half, hand, hang, happen, happy, hard,
have, he, head, health, hear, heart, heat, heavy, help, her, here, herself,
high, him, himself, his, history, hit, hold, home, hope, hospital, hot, hotel,
hour, house, how, however, huge, human, hundred, husband, I, idea, identify, if,
image, imagine, impact, important, improve, in, include, including, increase,
indeed, indicate, individual, industry, information, inside, instead,
institution, interest, interesting, international, interview, into, investment,
involve, issue, it, item, its, itself, job, join, just, keep, key, kid, kill,
kind, kitchen, know, knowledge, land, language, large, last, late, later, laugh,
law, lawyer, lay, lead, leader, learn, least, leave, left, leg, legal, less,
let, letter, level, lie, life, light, like, likely, line, list, listen, little,
live, local, long, look, lose, loss, lot, love, low, machine, magazine, main,
maintain, major, majority, make, man, manage, management, manager, many, market,
marriage, material, matter, may, maybe, me, mean, measure, media, medical, meet,
meeting, member, memory, mention, message, method, middle, might, military,
million, mind, minute, miss, mission, model, modern, moment, money, month, more,
morning, most, mother, mouth, move, movement, movie, Mr, Mrs, much, music, must,
my, myself, name, nation, national, natural, nature, near, nearly, necessary,
need, network, never, new, news, newspaper, next, nice, night, no, none, nor,
north, not, note, nothing, notice, now, n't, number, occur, of, off, offer,
office, officer, official, often, oh, oil, ok, old, on, once, one, only, onto,
open, operation, opportunity, option, or, order, organization, other, others,
our, out, outside, over, own, owner, page, pain, painting, paper, parent, part,
participant, particular, particularly, partner, party, pass, past, patient,
pattern, pay, peace, people, per, perform, performance, perhaps, period, person,
personal, phone, physical, pick, picture, piece, place, plan, plant, play,
player, PM, point, police, policy, political, politics, poor, popular,
population, position, positive, possible, power, practice, prepare, present,
president, pressure, pretty, prevent, price, private, probably, problem,
process, produce, product, production, professional, professor, program,
project, property, protect, prove, provide, public, pull, purpose, push, put,
quality, question, quickly, quite, race, radio, raise, range, rate, rather,
reach, read, ready, real, reality, realize, really, reason, receive, recent,
recently, recognize, record, red, reduce, reflect, region, relate, relationship,
religious, remain, remember, remove, report, represent, Republican, require,
research, resource, respond, response, responsibility, rest, result, return,
reveal, rich, right, rise, risk, road, rock, role, room, rule, run, safe, same,
save, say, scene, school, science, scientist, score, sea, season, seat, second,
section, security, see, seek, seem, sell, send, senior, sense, series, serious,
serve, service, set, seven, several, sex, sexual, shake, share, she, shoot,
short, shot, should, shoulder, show, side, sign, significant, similar, simple,
simply, since, sing, single, sister, sit, site, situation, six, size, skill,
skin, small, smile, so, social, society, soldier, some, somebody, someone,
something, sometimes, son, song, soon, sort, sound, source, south, southern,
space, speak, special, specific, speech, spend, sport, spring, staff, stage,
stand, standard, star, start, state, statement, station, stay, step, still,
stock, stop, store, story, strategy, street, strong, structure, student, study,
stuff, style, subject, success, successful, such, suddenly, suffer, suggest,
summer, support, sure, surface, system, table, take, talk, task, tax, teach,
teacher, team, technology, television, tell, ten, tend, term, test, than, thank,
that, the, their, them, themselves, then, theory, there, these, they, thing,
think, third, this, those, though, thought, thousand, threat, three, through,
throughout, throw, thus, time, to, today, together, tonight, too, top, total,
tough, toward, town, trade, traditional, training, travel, treat, treatment,
tree, trial, trip, trouble, true, truth, try, turn, TV, two, type, under,
understand, unit, until, up, upon, us, use, usually, value, various, very,
victim, view, violence, visit, voice, vote, wait, walk, wall, want, war, watch,
water, way, we, weapon, wear, week, weight, well, west, western, what, whatever,
when, where, whether, which, while, white, who, whole, whom, whose, why, wide,
wife, will, win, wind, window, wish, with, within, without, woman, wonder, word,
work, worker, world, worry, would, write, writer, wrong, yard, yeah, year, yes,
yet, you, young, your, yourself"#
.split(',')
.map(|word| word.trim())
.collect();
let mut word_pairs: Vec<_> = Vec::new();
for word in &words {
for other_word in &words {
word_pairs.push(word.to_string() + " " + other_word);
}
}
assert_eq!(1_000_000, word_pairs.len());
word_pairs
}
#[allow(unused)] // False positive
fn test_hash_common_words<B: BuildHasher>(build_hasher: &B) {
let word_pairs: Vec<_> = gen_word_pairs();
check_for_collisions(build_hasher, &word_pairs, 32);
}
#[allow(unused)] // False positive
fn check_for_collisions<H: Hash, B: BuildHasher>(build_hasher: &B, items: &[H], bucket_count: usize) {
let mut buckets = vec![0; bucket_count];
for item in items {
let value = hash(item, build_hasher) as usize;
buckets[value % bucket_count] += 1;
}
let mean = items.len() / bucket_count;
let max = *buckets.iter().max().unwrap();
let min = *buckets.iter().min().unwrap();
assert!(
(min as f64) > (mean as f64) * 0.95,
"min: {}, max:{}, {:?}",
min,
max,
buckets
);
assert!(
(max as f64) < (mean as f64) * 1.05,
"min: {}, max:{}, {:?}",
min,
max,
buckets
);
}
#[cfg(specialize)]
#[allow(unused)] // False positive
fn hash<H: Hash, B: BuildHasher>(b: &H, build_hasher: &B) -> u64 {
build_hasher.hash_one(b)
}
#[cfg(not(specialize))]
#[allow(unused)] // False positive
fn hash<H: Hash, B: BuildHasher>(b: &H, build_hasher: &B) -> u64 {
let mut hasher = build_hasher.build_hasher();
b.hash(&mut hasher);
hasher.finish()
}
#[test]
fn test_bucket_distribution() {
let build_hasher = RandomState::with_seeds(1, 2, 3, 4);
test_hash_common_words(&build_hasher);
let sequence: Vec<_> = (0..320000).collect();
check_for_collisions(&build_hasher, &sequence, 32);
let sequence: Vec<_> = (0..2560000).collect();
check_for_collisions(&build_hasher, &sequence, 256);
let sequence: Vec<_> = (0..320000).map(|i| i * 1024).collect();
check_for_collisions(&build_hasher, &sequence, 32);
let sequence: Vec<_> = (0..2560000_u64).map(|i| i * 1024).collect();
check_for_collisions(&build_hasher, &sequence, 256);
}
#[cfg(feature = "std")]
#[test]
fn test_ahash_alias_map_construction() {
let mut map = ahash::HashMap::default();
map.insert(1, "test");
use ahash::HashMapExt;
let mut map = ahash::HashMap::with_capacity(1234);
map.insert(1, "test");
}
#[cfg(feature = "std")]
#[test]
fn test_ahash_alias_set_construction() {
let mut set = ahash::HashSet::default();
set.insert(1);
use ahash::HashSetExt;
let mut set = ahash::HashSet::with_capacity(1235);
set.insert(1);
}
#[cfg(feature = "std")]
#[test]
fn test_key_ref() {
let mut map = ahash::HashMap::default();
map.insert(1, "test");
assert_eq!(Some((1, "test")), map.remove_entry(&1));
let mut map = ahash::HashMap::default();
map.insert(&1, "test");
assert_eq!(Some((&1, "test")), map.remove_entry(&&1));
let mut m = ahash::HashSet::<Box<String>>::default();
m.insert(Box::from("hello".to_string()));
assert!(m.contains(&"hello".to_string()));
let mut m = ahash::HashSet::<String>::default();
m.insert("hello".to_string());
assert!(m.contains("hello"));
let mut m = ahash::HashSet::<Box<[u8]>>::default();
m.insert(Box::from(&b"hello"[..]));
assert!(m.contains(&b"hello"[..]));
}
#[cfg(feature = "std")]
#[test]
fn test_byte_dist() {
use rand::{SeedableRng, Rng, RngCore};
use pcg_mwc::Mwc256XXA64;
let mut r = Mwc256XXA64::seed_from_u64(0xe786_c22b_119c_1479);
let mut lowest = 2.541;
let mut highest = 2.541;
for _round in 0..100 {
let mut table: [bool; 256 * 8] = [false; 256 * 8];
let hasher = RandomState::with_seeds(r.gen(), r.gen(), r.gen(), r.gen());
for i in 0..128 {
let mut keys: [u8; 8] = hasher.hash_one((i as u64) << 30).to_ne_bytes();
//let mut keys = r.next_u64().to_ne_bytes(); //This is a control to test assert sensitivity.
for idx in 0..8 {
while table[idx * 256 + keys[idx] as usize] {
keys[idx] = keys[idx].wrapping_add(1);
}
table[idx * 256 + keys[idx] as usize] = true;
}
}
for idx in 0..8 {
let mut len = 0;
let mut total_len = 0;
let mut num_seq = 0;
for i in 0..256 {
if table[idx * 256 + i] {
len += 1;
} else if len != 0 {
num_seq += 1;
total_len += len;
len = 0;
}
}
let mean = total_len as f32 / num_seq as f32;
println!("Mean sequence length = {}", mean);
if mean > highest {
highest = mean;
}
if mean < lowest {
lowest = mean;
}
}
}
assert!(lowest > 1.9, "Lowest = {}", lowest);
assert!(highest < 3.9, "Highest = {}", highest);
}
fn ahash_vec<H: Hash>(b: &Vec<H>) -> u64 {
let mut total: u64 = 0;
for item in b {
let mut hasher = RandomState::with_seeds(12, 34, 56, 78).build_hasher();
item.hash(&mut hasher);
total = total.wrapping_add(hasher.finish());
}
total
}
fn fxhash_vec<H: Hash>(b: &Vec<H>) -> u64 {
let mut total: u64 = 0;
for item in b {
let mut hasher = FxHasher::default();
item.hash(&mut hasher);
total = total.wrapping_add(hasher.finish());
}
total
}
fn bench_ahash_words(c: &mut Criterion) {
let words = gen_word_pairs();
c.bench_function("aes_words", |b| b.iter(|| black_box(ahash_vec(&words))));
}
fn bench_fx_words(c: &mut Criterion) {
let words = gen_word_pairs();
c.bench_function("fx_words", |b| b.iter(|| black_box(fxhash_vec(&words))));
}
criterion_main!(benches);
criterion_group!(benches, bench_ahash_words, bench_fx_words,);
+81
View File
@@ -0,0 +1,81 @@
use ahash::{AHasher, RandomState};
use std::hash::{BuildHasher, Hash, Hasher};
#[macro_use]
extern crate no_panic;
#[inline(never)]
#[no_panic]
fn hash_test_final(num: i32, string: &str) -> (u64, u64) {
use core::hash::Hasher;
let mut hasher1 = RandomState::with_seeds(1, 2, 3, 4).build_hasher();
let mut hasher2 = RandomState::with_seeds(3, 4, 5, 6).build_hasher();
hasher1.write_i32(num);
hasher2.write(string.as_bytes());
(hasher1.finish(), hasher2.finish())
}
#[inline(never)]
fn hash_test_final_wrapper(num: i32, string: &str) {
hash_test_final(num, string);
}
struct SimpleBuildHasher {
hasher: AHasher,
}
impl SimpleBuildHasher {
fn hash_one<T: Hash>(&self, x: T) -> u64
where
Self: Sized,
{
let mut hasher = self.build_hasher();
x.hash(&mut hasher);
hasher.finish()
}
}
impl BuildHasher for SimpleBuildHasher {
type Hasher = AHasher;
fn build_hasher(&self) -> Self::Hasher {
self.hasher.clone()
}
}
#[inline(never)]
#[no_panic]
fn hash_test_specialize(num: i32, string: &str) -> (u64, u64) {
let hasher1 = RandomState::with_seeds(1, 2, 3, 4).build_hasher();
let hasher2 = RandomState::with_seeds(1, 2, 3, 4).build_hasher();
(
SimpleBuildHasher { hasher: hasher1 }.hash_one(num),
SimpleBuildHasher { hasher: hasher2 }.hash_one(string.as_bytes()),
)
}
#[inline(never)]
fn hash_test_random_wrapper(num: i32, string: &str) {
hash_test_specialize(num, string);
}
#[inline(never)]
#[no_panic]
fn hash_test_random(num: i32, string: &str) -> (u64, u64) {
let build_hasher1 = RandomState::with_seeds(1, 2, 3, 4);
let build_hasher2 = RandomState::with_seeds(1, 2, 3, 4);
(build_hasher1.hash_one(&num), build_hasher2.hash_one(string.as_bytes()))
}
#[inline(never)]
fn hash_test_specialize_wrapper(num: i32, string: &str) {
hash_test_specialize(num, string);
}
#[test]
fn test_no_panic() {
hash_test_final_wrapper(2, "Foo");
hash_test_specialize_wrapper(2, "Bar");
hash_test_random(2, "Baz");
hash_test_random_wrapper(2, "Bat");
}
+1
View File
@@ -0,0 +1 @@
{"files":{".cargo_vcs_info.json":"f5e9f49ba26ab09e3432ae424ecb09eeb39bf0dcbe8e2bdc2335db4dee8eb7df",".github/FUNDING.yml":"b017158736b3c9751a2d21edfce7fe61c8954e2fced8da8dd3013c2f3e295bd9",".github/workflows/ci.yml":"3906e0b1d66cd99e62f9a1d1455ff0c5181dd7ce6abd07f5f9d392d93e6d66c2","Cargo.lock":"f03ce351fa726aa6a443c0e8e20e2aac653a95dbc8225dcd497390106584b5ae","Cargo.toml":"a89e4444e863507cfd9d4df741a9512b5db220aefe75311bd402c787a22a0bfc","Cargo.toml.orig":"9dd604a6cb912032e6ccd873385b2f3339c46c8ece69f876af4ce535dc20d612","LICENSE-APACHE":"62c7a1e35f56406896d7aa7ca52d0cc0d272ac022b5d2796e7d6905db8a3636a","LICENSE-MIT":"23f18e03dc49df91622fe2a76176497404e46ced8a715d9d2b67a7446571cca3","README.md":"86f5b88c45b2b9b7eefa0ad66c11918a607319cad683f01123ff38db68b4a49b","build.rs":"89d1baedcff59822ed692d3a33f131c211a213bdec33c83f68b54110d9c64223","rust-toolchain.toml":"6bbb61302978c736b2da03e4fb40e3beab908f85d533ab46fd541e637b5f3e0f","src/backtrace.rs":"621ec5d9e2f31883d0384ae00fc536d1ea359da989d2e0d1be1ba03482c8f920","src/chain.rs":"85af447405f075633fab186b7f1c94d7f33a36474f239c50a961b2d6197d5426","src/context.rs":"8b106e43df7a66fbc3e85b87a4d7f8a780d43d3ee0cd1ae86c571950ecf07d13","src/ensure.rs":"709524741c8f9365e01cb36fc2349a8724513de02cf9e253809a93af8d6a7b6e","src/error.rs":"38aaae2c0067c25137d6528bb1e32dc60122efe285559d50bbca854a0656d8fc","src/fmt.rs":"2cca8c7f3cab636fdb17433ca2b1fda0e8d28bb20d4647b87c991dacd8f06cdf","src/kind.rs":"ddcabf15b3b47a4ef16ed361c45cc0cc82e7be90622995ff6c04b3fec78bb04f","src/lib.rs":"d03bbd1141aac19f344606daa73eb389d54dd26ee27cd13143d1eddb1a045297","src/macros.rs":"696e6a8aac614525a29697aedc7561e98beccf3d43aed3ada1cc93daa99da8ba","src/nightly.rs":"1c0aeadfcdbe22f8f807b185dec1a7448c378bddb37689714944b60473b58049","src/ptr.rs":"a114639d26f4feca7e186106342334cdf2e2176f892149aa331ace72e5d81410","src/wrapper.rs":"079218e1d8c9374f52311b1c7971f7fbdeba4d7af1b0749c0d535010f1232697","tests/common/mod.rs":"f9088c2d7afafa64ff730b629272045b776bfafc2f5957508242da630635f2e1","tests/compiletest.rs":"4e381aa8ca3eabb7ac14d1e0c3700b3223e47640547a6988cfa13ad68255f60f","tests/drop/mod.rs":"08c3e553c1cc0d2dbd936fc45f4b5b1105057186affd6865e8d261e05f0f0646","tests/test_autotrait.rs":"ba9bc18416115cb48fd08675a3e7fc89584de7926dad6b2be6645dc13d5931df","tests/test_backtrace.rs":"60afdd7ee5850dc22625ff486fe41c47fd322db874a93c4871ddfed2bf603930","tests/test_boxed.rs":"6b26db0e2eb72afe9af7352ea820837aab90f8d486294616dd5dc34c1b94038c","tests/test_chain.rs":"3a8a8d7569913bd98c0e27c69d0bda35101e7fde7c056ed57cdd8ed018e4cbcb","tests/test_context.rs":"8409c53b328562c11e822bd6c3cd17e0d4d50b9bbb8fc3617333fd77303a6a33","tests/test_convert.rs":"7e7a8b4772a427a911014ac4d1083f9519000e786177f898808980dd9bdfde61","tests/test_downcast.rs":"797e69a72d125758c4c4897e5dc776d549d52cc9a6a633e0a33193f588a62b88","tests/test_ensure.rs":"6ef8823153d6aba31dda8438b9e51984e6b7377d585addbaf87162b39d733cd9","tests/test_ffi.rs":"c6e6bf39e6b2cf9aacea12e3e4da4e1e1edb7fc906d231e5baf45812a08de3c4","tests/test_fmt.rs":"0e49b48f08e4faaf03e2f202e1efc5250018876c4e1b01b8379d7a38ae8df870","tests/test_macros.rs":"3eb6ec9024cf8812da48de35b733d5a69ed942907fb4e6c67ec471cd4bcef562","tests/test_repr.rs":"034dee888abd08741e11ac2e95ef4fcb2ab3943d0a76e8e976db404658e1a252","tests/test_source.rs":"b80723cf635a4f8c4df21891b34bfab9ed2b2aa407e7a2f826d24e334cd5f88e","tests/ui/chained-comparison.rs":"6504b03d95b5acc232a7f4defc9f343b2be6733bf475fa0992e8e6545b912bd4","tests/ui/chained-comparison.stderr":"7f1d0a8c251b0ede2d30b3087ec157fc660945c97a642c4a5acf5a14ec58de34","tests/ui/empty-ensure.rs":"ab5bf37c846a0d689f26ce9257a27228411ed64154f9c950f1602d88a355d94b","tests/ui/empty-ensure.stderr":"315782f5f4246290fe190e3767b22c3dcaffaabc19c5ace0373537d53e765278","tests/ui/ensure-nonbool.rs":"7e57cb93fbcd82959b36586ed6bd2ad978b051fe5facd5274651fde6b1600905","tests/ui/ensure-nonbool.stderr":"e3dfbaedd66fdfc524f8462305760c7978c4268ca7e1cd872d7ecde5d3b455bd","tests/ui/must-use.rs":"fb59860b43f673bf4a430a6036ba463e95028844d8dd4243cfe5ebc7f2be582f","tests/ui/must-use.stderr":"8b1cb7fca632396a22bb3a642f2df3ab62b3e9a87951e20fa40bdd548d002162","tests/ui/no-impl.rs":"fab6cbf2f6ea510b86f567dfb3b7c31250a9fd71ae5d110dbb9188be569ec593","tests/ui/no-impl.stderr":"c472de95483aa234728c7f57039c88fc3456892838329976a75cd167c3c172c9","tests/ui/temporary-value.rs":"4dcc96271b2403e6372cf4cfc813445e5ce4365fc6e156b6bc38274098499a70","tests/ui/temporary-value.stderr":"7f99cd914d2d423d77274e1b859b5ad151cde8224e42687e50622c26118fd47f","tests/ui/wrong-interpolation.rs":"9c44d4674c2dccd27b9dedd03341346ec02d993b41793ee89b5755202e7e367e","tests/ui/wrong-interpolation.stderr":"301e60e2eb9401782c7dc0b3580613a4cb2aafd4cc8065734a630a62e1161aa5"},"package":"7f202df86484c868dbad7eaa557ef785d5c66295e41b460ef922eca0723b842c"}
+6
View File
@@ -0,0 +1,6 @@
{
"git": {
"sha1": "5c657b32522023a9f7ef883fb08582fd8e656b1a"
},
"path_in_vcs": ""
}
+1
View File
@@ -0,0 +1 @@
github: dtolnay
+146
View File
@@ -0,0 +1,146 @@
name: CI
on:
push:
pull_request:
workflow_dispatch:
schedule: [cron: "40 1 * * *"]
permissions:
contents: read
env:
RUSTFLAGS: -Dwarnings
jobs:
pre_ci:
uses: dtolnay/.github/.github/workflows/pre_ci.yml@master
test:
name: Rust ${{matrix.rust}}
needs: pre_ci
if: needs.pre_ci.outputs.continue
runs-on: ubuntu-latest
strategy:
fail-fast: false
matrix:
rust: [nightly, beta, stable, 1.82.0, 1.80.0, 1.76.0]
timeout-minutes: 45
steps:
- uses: actions/checkout@v6
- uses: dtolnay/rust-toolchain@master
with:
toolchain: ${{matrix.rust}}
components: rust-src
- name: Enable type layout randomization
run: echo RUSTFLAGS=${RUSTFLAGS}\ -Zrandomize-layout >> $GITHUB_ENV
if: matrix.rust == 'nightly'
- name: Enable nightly-only tests
run: echo RUSTFLAGS=${RUSTFLAGS}\ --cfg=anyhow_nightly_testing >> $GITHUB_ENV
if: matrix.rust == 'nightly'
- run: cargo test
- run: cargo check --no-default-features
- run: cargo check
- uses: actions/upload-artifact@v6
if: matrix.rust == 'nightly' && always()
with:
name: Cargo.lock
path: Cargo.lock
continue-on-error: true
build:
name: Rust ${{matrix.rust}}
needs: pre_ci
if: needs.pre_ci.outputs.continue
runs-on: ubuntu-latest
strategy:
fail-fast: false
matrix:
rust: [1.68.0]
timeout-minutes: 45
steps:
- uses: actions/checkout@v6
- uses: dtolnay/rust-toolchain@master
with:
toolchain: ${{matrix.rust}}
components: rust-src
- run: cargo check --manifest-path tests/crate/Cargo.toml
- run: cargo check --manifest-path tests/crate/Cargo.toml --no-default-features
minimal:
name: Minimal versions
needs: pre_ci
if: needs.pre_ci.outputs.continue
runs-on: ubuntu-latest
timeout-minutes: 45
steps:
- uses: actions/checkout@v6
- uses: dtolnay/rust-toolchain@nightly
- run: cargo generate-lockfile -Z minimal-versions
- run: cargo check --locked
windows:
name: Windows
needs: pre_ci
if: needs.pre_ci.outputs.continue
runs-on: windows-latest
timeout-minutes: 45
steps:
- uses: actions/checkout@v6
- uses: dtolnay/rust-toolchain@stable
with:
components: rust-src
- run: cargo check
doc:
name: Documentation
needs: pre_ci
if: needs.pre_ci.outputs.continue
runs-on: ubuntu-latest
timeout-minutes: 45
env:
RUSTDOCFLAGS: -Dwarnings
steps:
- uses: actions/checkout@v6
- uses: dtolnay/rust-toolchain@nightly
with:
components: rust-src
- uses: dtolnay/install@cargo-docs-rs
- run: cargo docs-rs
clippy:
name: Clippy
runs-on: ubuntu-latest
if: github.event_name != 'pull_request'
timeout-minutes: 45
steps:
- uses: actions/checkout@v6
- uses: dtolnay/rust-toolchain@nightly
with:
components: clippy, rust-src
- run: cargo clippy --tests -- -Dclippy::all -Dclippy::pedantic
miri:
name: Miri
needs: pre_ci
if: needs.pre_ci.outputs.continue
runs-on: ubuntu-latest
timeout-minutes: 45
steps:
- uses: actions/checkout@v6
- uses: dtolnay/rust-toolchain@miri
- run: cargo miri setup
- run: cargo miri test
env:
MIRIFLAGS: -Zmiri-strict-provenance
outdated:
name: Outdated
runs-on: ubuntu-latest
if: github.event_name != 'pull_request'
timeout-minutes: 45
steps:
- uses: actions/checkout@v6
- uses: dtolnay/rust-toolchain@stable
- uses: dtolnay/install@cargo-outdated
- run: cargo outdated --workspace --exit-code 1
Generated Vendored
+344
View File
@@ -0,0 +1,344 @@
# This file is automatically @generated by Cargo.
# It is not intended for manual editing.
version = 3
[[package]]
name = "anyhow"
version = "1.0.102"
dependencies = [
"futures",
"rustversion",
"syn",
"thiserror",
"trybuild",
]
[[package]]
name = "dissimilar"
version = "1.0.10"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "8975ffdaa0ef3661bfe02dbdcc06c9f829dfafe6a3c474de366a8d5e44276921"
[[package]]
name = "equivalent"
version = "1.0.2"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "877a4ace8713b0bcf2a4e7eec82529c029f1d0619886d18145fea96c3ffe5c0f"
[[package]]
name = "futures"
version = "0.3.32"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "8b147ee9d1f6d097cef9ce628cd2ee62288d963e16fb287bd9286455b241382d"
dependencies = [
"futures-channel",
"futures-core",
"futures-io",
"futures-sink",
"futures-task",
"futures-util",
]
[[package]]
name = "futures-channel"
version = "0.3.32"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "07bbe89c50d7a535e539b8c17bc0b49bdb77747034daa8087407d655f3f7cc1d"
dependencies = [
"futures-core",
"futures-sink",
]
[[package]]
name = "futures-core"
version = "0.3.32"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "7e3450815272ef58cec6d564423f6e755e25379b217b0bc688e295ba24df6b1d"
[[package]]
name = "futures-io"
version = "0.3.32"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "cecba35d7ad927e23624b22ad55235f2239cfa44fd10428eecbeba6d6a717718"
[[package]]
name = "futures-sink"
version = "0.3.32"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "c39754e157331b013978ec91992bde1ac089843443c49cbc7f46150b0fad0893"
[[package]]
name = "futures-task"
version = "0.3.32"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "037711b3d59c33004d3856fbdc83b99d4ff37a24768fa1be9ce3538a1cde4393"
[[package]]
name = "futures-util"
version = "0.3.32"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "389ca41296e6190b48053de0321d02a77f32f8a5d2461dd38762c0593805c6d6"
dependencies = [
"futures-core",
"futures-sink",
"futures-task",
"pin-project-lite",
]
[[package]]
name = "glob"
version = "0.3.3"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "0cc23270f6e1808e30a928bdc84dea0b9b4136a8bc82338574f23baf47bbd280"
[[package]]
name = "hashbrown"
version = "0.16.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "841d1cc9bed7f9236f321df977030373f4a4163ae1a7dbfe1a51a2c1a51d9100"
[[package]]
name = "indexmap"
version = "2.13.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "7714e70437a7dc3ac8eb7e6f8df75fd8eb422675fc7678aff7364301092b1017"
dependencies = [
"equivalent",
"hashbrown",
]
[[package]]
name = "itoa"
version = "1.0.17"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "92ecc6618181def0457392ccd0ee51198e065e016d1d527a7ac1b6dc7c1f09d2"
[[package]]
name = "memchr"
version = "2.8.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "f8ca58f447f06ed17d5fc4043ce1b10dd205e060fb3ce5b979b8ed8e59ff3f79"
[[package]]
name = "pin-project-lite"
version = "0.2.16"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "3b3cff922bd51709b605d9ead9aa71031d81447142d828eb4a6eba76fe619f9b"
[[package]]
name = "proc-macro2"
version = "1.0.106"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "8fd00f0bb2e90d81d1044c2b32617f68fcb9fa3bb7640c23e9c748e53fb30934"
dependencies = [
"unicode-ident",
]
[[package]]
name = "quote"
version = "1.0.44"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "21b2ebcf727b7760c461f091f9f0f539b77b8e87f2fd88131e7f1b433b3cece4"
dependencies = [
"proc-macro2",
]
[[package]]
name = "rustversion"
version = "1.0.22"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "b39cdef0fa800fc44525c84ccb54a029961a8215f9619753635a9c0d2538d46d"
[[package]]
name = "serde"
version = "1.0.228"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "9a8e94ea7f378bd32cbbd37198a4a91436180c5bb472411e48b5ec2e2124ae9e"
dependencies = [
"serde_core",
]
[[package]]
name = "serde_core"
version = "1.0.228"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "41d385c7d4ca58e59fc732af25c3983b67ac852c1a25000afe1175de458b67ad"
dependencies = [
"serde_derive",
]
[[package]]
name = "serde_derive"
version = "1.0.228"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "d540f220d3187173da220f885ab66608367b6574e925011a9353e4badda91d79"
dependencies = [
"proc-macro2",
"quote",
"syn",
]
[[package]]
name = "serde_json"
version = "1.0.149"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "83fc039473c5595ace860d8c4fafa220ff474b3fc6bfdb4293327f1a37e94d86"
dependencies = [
"itoa",
"memchr",
"serde",
"serde_core",
"zmij",
]
[[package]]
name = "serde_spanned"
version = "1.0.4"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "f8bbf91e5a4d6315eee45e704372590b30e260ee83af6639d64557f51b067776"
dependencies = [
"serde_core",
]
[[package]]
name = "syn"
version = "2.0.116"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "3df424c70518695237746f84cede799c9c58fcb37450d7b23716568cc8bc69cb"
dependencies = [
"proc-macro2",
"quote",
"unicode-ident",
]
[[package]]
name = "target-triple"
version = "1.0.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "591ef38edfb78ca4771ee32cf494cb8771944bee237a9b91fc9c1424ac4b777b"
[[package]]
name = "termcolor"
version = "1.4.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "06794f8f6c5c898b3275aebefa6b8a1cb24cd2c6c79397ab15774837a0bc5755"
dependencies = [
"winapi-util",
]
[[package]]
name = "thiserror"
version = "2.0.18"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "4288b5bcbc7920c07a1149a35cf9590a2aa808e0bc1eafaade0b80947865fbc4"
dependencies = [
"thiserror-impl",
]
[[package]]
name = "thiserror-impl"
version = "2.0.18"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "ebc4ee7f67670e9b64d05fa4253e753e016c6c95ff35b89b7941d6b856dec1d5"
dependencies = [
"proc-macro2",
"quote",
"syn",
]
[[package]]
name = "toml"
version = "1.0.3+spec-1.1.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "c7614eaf19ad818347db24addfa201729cf2a9b6fdfd9eb0ab870fcacc606c0c"
dependencies = [
"indexmap",
"serde_core",
"serde_spanned",
"toml_datetime",
"toml_parser",
"toml_writer",
"winnow",
]
[[package]]
name = "toml_datetime"
version = "1.0.0+spec-1.1.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "32c2555c699578a4f59f0cc68e5116c8d7cabbd45e1409b989d4be085b53f13e"
dependencies = [
"serde_core",
]
[[package]]
name = "toml_parser"
version = "1.0.9+spec-1.1.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "702d4415e08923e7e1ef96cd5727c0dfed80b4d2fa25db9647fe5eb6f7c5a4c4"
dependencies = [
"winnow",
]
[[package]]
name = "toml_writer"
version = "1.0.6+spec-1.1.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "ab16f14aed21ee8bfd8ec22513f7287cd4a91aa92e44edfe2c17ddd004e92607"
[[package]]
name = "trybuild"
version = "1.0.116"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "47c635f0191bd3a2941013e5062667100969f8c4e9cd787c14f977265d73616e"
dependencies = [
"dissimilar",
"glob",
"serde",
"serde_derive",
"serde_json",
"target-triple",
"termcolor",
"toml",
]
[[package]]
name = "unicode-ident"
version = "1.0.24"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "e6e4313cd5fcd3dad5cafa179702e2b244f760991f45397d14d4ebf38247da75"
[[package]]
name = "winapi-util"
version = "0.1.11"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "c2a7b1c03c876122aa43f3020e6c3c3ee5c05081c9a00739faf7503aeba10d22"
dependencies = [
"windows-sys",
]
[[package]]
name = "windows-link"
version = "0.2.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "f0805222e57f7521d6a62e36fa9163bc891acd422f971defe97d64e70d0a4fe5"
[[package]]
name = "windows-sys"
version = "0.61.2"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "ae137229bcbd6cdf0f7b80a31df61766145077ddf49416a728b02cb3921ff3fc"
dependencies = [
"windows-link",
]
[[package]]
name = "winnow"
version = "0.7.14"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "5a5364e9d77fcdeeaa6062ced926ee3381faa2ee02d3eb83a5c27a8825540829"
[[package]]
name = "zmij"
version = "1.0.21"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "b8848ee67ecc8aedbaf3e4122217aff892639231befc6a1b58d29fff4c2cabaa"
+129
View File
@@ -0,0 +1,129 @@
# THIS FILE IS AUTOMATICALLY GENERATED BY CARGO
#
# When uploading crates to the registry Cargo will automatically
# "normalize" Cargo.toml files for maximal compatibility
# with all versions of Cargo and also rewrite `path` dependencies
# to registry (e.g., crates.io) dependencies.
#
# If you are reading this file be aware that the original Cargo.toml
# will likely look very different (and much more reasonable).
# See Cargo.toml.orig for the original contents.
[package]
edition = "2021"
rust-version = "1.68"
name = "anyhow"
version = "1.0.102"
authors = ["David Tolnay <dtolnay@gmail.com>"]
build = "build.rs"
autolib = false
autobins = false
autoexamples = false
autotests = false
autobenches = false
description = "Flexible concrete Error type built on std::error::Error"
documentation = "https://docs.rs/anyhow"
readme = "README.md"
keywords = [
"error",
"error-handling",
]
categories = [
"rust-patterns",
"no-std",
]
license = "MIT OR Apache-2.0"
repository = "https://github.com/dtolnay/anyhow"
[package.metadata.docs.rs]
targets = ["x86_64-unknown-linux-gnu"]
rustdoc-args = [
"--generate-link-to-definition",
"--generate-macro-expansion",
"--extern-html-root-url=core=https://doc.rust-lang.org",
"--extern-html-root-url=alloc=https://doc.rust-lang.org",
"--extern-html-root-url=std=https://doc.rust-lang.org",
]
[features]
backtrace = []
default = ["std"]
std = []
[lib]
name = "anyhow"
path = "src/lib.rs"
[[test]]
name = "compiletest"
path = "tests/compiletest.rs"
[[test]]
name = "test_autotrait"
path = "tests/test_autotrait.rs"
[[test]]
name = "test_backtrace"
path = "tests/test_backtrace.rs"
[[test]]
name = "test_boxed"
path = "tests/test_boxed.rs"
[[test]]
name = "test_chain"
path = "tests/test_chain.rs"
[[test]]
name = "test_context"
path = "tests/test_context.rs"
[[test]]
name = "test_convert"
path = "tests/test_convert.rs"
[[test]]
name = "test_downcast"
path = "tests/test_downcast.rs"
[[test]]
name = "test_ensure"
path = "tests/test_ensure.rs"
[[test]]
name = "test_ffi"
path = "tests/test_ffi.rs"
[[test]]
name = "test_fmt"
path = "tests/test_fmt.rs"
[[test]]
name = "test_macros"
path = "tests/test_macros.rs"
[[test]]
name = "test_repr"
path = "tests/test_repr.rs"
[[test]]
name = "test_source"
path = "tests/test_source.rs"
[dev-dependencies.futures]
version = "0.3"
default-features = false
[dev-dependencies.rustversion]
version = "1.0.6"
[dev-dependencies.syn]
version = "2.0"
features = ["full"]
[dev-dependencies.thiserror]
version = "2"
[dev-dependencies.trybuild]
version = "1.0.108"
features = ["diff"]
+176
View File
@@ -0,0 +1,176 @@
Apache License
Version 2.0, January 2004
http://www.apache.org/licenses/
TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION
1. Definitions.
"License" shall mean the terms and conditions for use, reproduction,
and distribution as defined by Sections 1 through 9 of this document.
"Licensor" shall mean the copyright owner or entity authorized by
the copyright owner that is granting the License.
"Legal Entity" shall mean the union of the acting entity and all
other entities that control, are controlled by, or are under common
control with that entity. For the purposes of this definition,
"control" means (i) the power, direct or indirect, to cause the
direction or management of such entity, whether by contract or
otherwise, or (ii) ownership of fifty percent (50%) or more of the
outstanding shares, or (iii) beneficial ownership of such entity.
"You" (or "Your") shall mean an individual or Legal Entity
exercising permissions granted by this License.
"Source" form shall mean the preferred form for making modifications,
including but not limited to software source code, documentation
source, and configuration files.
"Object" form shall mean any form resulting from mechanical
transformation or translation of a Source form, including but
not limited to compiled object code, generated documentation,
and conversions to other media types.
"Work" shall mean the work of authorship, whether in Source or
Object form, made available under the License, as indicated by a
copyright notice that is included in or attached to the work
(an example is provided in the Appendix below).
"Derivative Works" shall mean any work, whether in Source or Object
form, that is based on (or derived from) the Work and for which the
editorial revisions, annotations, elaborations, or other modifications
represent, as a whole, an original work of authorship. For the purposes
of this License, Derivative Works shall not include works that remain
separable from, or merely link (or bind by name) to the interfaces of,
the Work and Derivative Works thereof.
"Contribution" shall mean any work of authorship, including
the original version of the Work and any modifications or additions
to that Work or Derivative Works thereof, that is intentionally
submitted to Licensor for inclusion in the Work by the copyright owner
or by an individual or Legal Entity authorized to submit on behalf of
the copyright owner. For the purposes of this definition, "submitted"
means any form of electronic, verbal, or written communication sent
to the Licensor or its representatives, including but not limited to
communication on electronic mailing lists, source code control systems,
and issue tracking systems that are managed by, or on behalf of, the
Licensor for the purpose of discussing and improving the Work, but
excluding communication that is conspicuously marked or otherwise
designated in writing by the copyright owner as "Not a Contribution."
"Contributor" shall mean Licensor and any individual or Legal Entity
on behalf of whom a Contribution has been received by Licensor and
subsequently incorporated within the Work.
2. Grant of Copyright License. Subject to the terms and conditions of
this License, each Contributor hereby grants to You a perpetual,
worldwide, non-exclusive, no-charge, royalty-free, irrevocable
copyright license to reproduce, prepare Derivative Works of,
publicly display, publicly perform, sublicense, and distribute the
Work and such Derivative Works in Source or Object form.
3. Grant of Patent License. Subject to the terms and conditions of
this License, each Contributor hereby grants to You a perpetual,
worldwide, non-exclusive, no-charge, royalty-free, irrevocable
(except as stated in this section) patent license to make, have made,
use, offer to sell, sell, import, and otherwise transfer the Work,
where such license applies only to those patent claims licensable
by such Contributor that are necessarily infringed by their
Contribution(s) alone or by combination of their Contribution(s)
with the Work to which such Contribution(s) was submitted. If You
institute patent litigation against any entity (including a
cross-claim or counterclaim in a lawsuit) alleging that the Work
or a Contribution incorporated within the Work constitutes direct
or contributory patent infringement, then any patent licenses
granted to You under this License for that Work shall terminate
as of the date such litigation is filed.
4. Redistribution. You may reproduce and distribute copies of the
Work or Derivative Works thereof in any medium, with or without
modifications, and in Source or Object form, provided that You
meet the following conditions:
(a) You must give any other recipients of the Work or
Derivative Works a copy of this License; and
(b) You must cause any modified files to carry prominent notices
stating that You changed the files; and
(c) You must retain, in the Source form of any Derivative Works
that You distribute, all copyright, patent, trademark, and
attribution notices from the Source form of the Work,
excluding those notices that do not pertain to any part of
the Derivative Works; and
(d) If the Work includes a "NOTICE" text file as part of its
distribution, then any Derivative Works that You distribute must
include a readable copy of the attribution notices contained
within such NOTICE file, excluding those notices that do not
pertain to any part of the Derivative Works, in at least one
of the following places: within a NOTICE text file distributed
as part of the Derivative Works; within the Source form or
documentation, if provided along with the Derivative Works; or,
within a display generated by the Derivative Works, if and
wherever such third-party notices normally appear. The contents
of the NOTICE file are for informational purposes only and
do not modify the License. You may add Your own attribution
notices within Derivative Works that You distribute, alongside
or as an addendum to the NOTICE text from the Work, provided
that such additional attribution notices cannot be construed
as modifying the License.
You may add Your own copyright statement to Your modifications and
may provide additional or different license terms and conditions
for use, reproduction, or distribution of Your modifications, or
for any such Derivative Works as a whole, provided Your use,
reproduction, and distribution of the Work otherwise complies with
the conditions stated in this License.
5. Submission of Contributions. Unless You explicitly state otherwise,
any Contribution intentionally submitted for inclusion in the Work
by You to the Licensor shall be under the terms and conditions of
this License, without any additional terms or conditions.
Notwithstanding the above, nothing herein shall supersede or modify
the terms of any separate license agreement you may have executed
with Licensor regarding such Contributions.
6. Trademarks. This License does not grant permission to use the trade
names, trademarks, service marks, or product names of the Licensor,
except as required for reasonable and customary use in describing the
origin of the Work and reproducing the content of the NOTICE file.
7. Disclaimer of Warranty. Unless required by applicable law or
agreed to in writing, Licensor provides the Work (and each
Contributor provides its Contributions) on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or
implied, including, without limitation, any warranties or conditions
of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A
PARTICULAR PURPOSE. You are solely responsible for determining the
appropriateness of using or redistributing the Work and assume any
risks associated with Your exercise of permissions under this License.
8. Limitation of Liability. In no event and under no legal theory,
whether in tort (including negligence), contract, or otherwise,
unless required by applicable law (such as deliberate and grossly
negligent acts) or agreed to in writing, shall any Contributor be
liable to You for damages, including any direct, indirect, special,
incidental, or consequential damages of any character arising as a
result of this License or out of the use or inability to use the
Work (including but not limited to damages for loss of goodwill,
work stoppage, computer failure or malfunction, or any and all
other commercial damages or losses), even if such Contributor
has been advised of the possibility of such damages.
9. Accepting Warranty or Additional Liability. While redistributing
the Work or Derivative Works thereof, You may choose to offer,
and charge a fee for, acceptance of support, warranty, indemnity,
or other liability obligations and/or rights consistent with this
License. However, in accepting such obligations, You may act only
on Your own behalf and on Your sole responsibility, not on behalf
of any other Contributor, and only if You agree to indemnify,
defend, and hold each Contributor harmless for any liability
incurred by, or claims asserted against, such Contributor by reason
of your accepting any such warranty or additional liability.
END OF TERMS AND CONDITIONS
+23
View File
@@ -0,0 +1,23 @@
Permission is hereby granted, free of charge, to any
person obtaining a copy of this software and associated
documentation files (the "Software"), to deal in the
Software without restriction, including without
limitation the rights to use, copy, modify, merge,
publish, distribute, sublicense, and/or sell copies of
the Software, and to permit persons to whom the Software
is furnished to do so, subject to the following
conditions:
The above copyright notice and this permission notice
shall be included in all copies or substantial portions
of the Software.
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF
ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED
TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A
PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT
SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY
CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR
IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER
DEALINGS IN THE SOFTWARE.
+179
View File
@@ -0,0 +1,179 @@
Anyhow&ensp;¯\\\_(°ペ)\_/¯
==========================
[<img alt="github" src="https://img.shields.io/badge/github-dtolnay/anyhow-8da0cb?style=for-the-badge&labelColor=555555&logo=github" height="20">](https://github.com/dtolnay/anyhow)
[<img alt="crates.io" src="https://img.shields.io/crates/v/anyhow.svg?style=for-the-badge&color=fc8d62&logo=rust" height="20">](https://crates.io/crates/anyhow)
[<img alt="docs.rs" src="https://img.shields.io/badge/docs.rs-anyhow-66c2a5?style=for-the-badge&labelColor=555555&logo=docs.rs" height="20">](https://docs.rs/anyhow)
[<img alt="build status" src="https://img.shields.io/github/actions/workflow/status/dtolnay/anyhow/ci.yml?branch=master&style=for-the-badge" height="20">](https://github.com/dtolnay/anyhow/actions?query=branch%3Amaster)
This library provides [`anyhow::Error`][Error], a trait object based error type
for easy idiomatic error handling in Rust applications.
[Error]: https://docs.rs/anyhow/1.0/anyhow/struct.Error.html
```toml
[dependencies]
anyhow = "1.0"
```
<br>
## Details
- Use `Result<T, anyhow::Error>`, or equivalently `anyhow::Result<T>`, as the
return type of any fallible function.
Within the function, use `?` to easily propagate any error that implements the
[`std::error::Error`] trait.
```rust
use anyhow::Result;
fn get_cluster_info() -> Result<ClusterMap> {
let config = std::fs::read_to_string("cluster.json")?;
let map: ClusterMap = serde_json::from_str(&config)?;
Ok(map)
}
```
[`std::error::Error`]: https://doc.rust-lang.org/std/error/trait.Error.html
- Attach context to help the person troubleshooting the error understand where
things went wrong. A low-level error like "No such file or directory" can be
annoying to debug without more context about what higher level step the
application was in the middle of.
```rust
use anyhow::{Context, Result};
fn main() -> Result<()> {
...
it.detach().context("Failed to detach the important thing")?;
let content = std::fs::read(path)
.with_context(|| format!("Failed to read instrs from {}", path))?;
...
}
```
```console
Error: Failed to read instrs from ./path/to/instrs.json
Caused by:
No such file or directory (os error 2)
```
- Downcasting is supported and can be by value, by shared reference, or by
mutable reference as needed.
```rust
// If the error was caused by redaction, then return a
// tombstone instead of the content.
match root_cause.downcast_ref::<DataStoreError>() {
Some(DataStoreError::Censored(_)) => Ok(Poll::Ready(REDACTED_CONTENT)),
None => Err(error),
}
```
- If using Rust &ge; 1.65, a backtrace is captured and printed with the error if
the underlying error type does not already provide its own. In order to see
backtraces, they must be enabled through the environment variables described
in [`std::backtrace`]:
- If you want panics and errors to both have backtraces, set
`RUST_BACKTRACE=1`;
- If you want only errors to have backtraces, set `RUST_LIB_BACKTRACE=1`;
- If you want only panics to have backtraces, set `RUST_BACKTRACE=1` and
`RUST_LIB_BACKTRACE=0`.
[`std::backtrace`]: https://doc.rust-lang.org/std/backtrace/index.html#environment-variables
- Anyhow works with any error type that has an impl of `std::error::Error`,
including ones defined in your crate. We do not bundle a `derive(Error)` macro
but you can write the impls yourself or use a standalone macro like
[thiserror].
```rust
use thiserror::Error;
#[derive(Error, Debug)]
pub enum FormatError {
#[error("Invalid header (expected {expected:?}, got {found:?})")]
InvalidHeader {
expected: String,
found: String,
},
#[error("Missing attribute: {0}")]
MissingAttribute(String),
}
```
- One-off error messages can be constructed using the `anyhow!` macro, which
supports string interpolation and produces an `anyhow::Error`.
```rust
return Err(anyhow!("Missing attribute: {}", missing));
```
A `bail!` macro is provided as a shorthand for the same early return.
```rust
bail!("Missing attribute: {}", missing);
```
<br>
## No-std support
In no_std mode, almost all of the same API is available and works the same way.
To depend on Anyhow in no_std mode, disable our default enabled "std" feature in
Cargo.toml. A global allocator is required.
```toml
[dependencies]
anyhow = { version = "1.0", default-features = false }
```
With versions of Rust older than 1.81, no_std mode may require an additional
`.map_err(Error::msg)` when working with a non-Anyhow error type inside a
function that returns Anyhow's error type, as the trait that `?`-based error
conversions are defined by is only available in std in those old versions.
<br>
## Comparison to failure
The `anyhow::Error` type works something like `failure::Error`, but unlike
failure ours is built around the standard library's `std::error::Error` trait
rather than a separate trait `failure::Fail`. The standard library has adopted
the necessary improvements for this to be possible as part of [RFC 2504].
[RFC 2504]: https://github.com/rust-lang/rfcs/blob/master/text/2504-fix-error.md
<br>
## Comparison to thiserror
Use Anyhow if you don't care what error type your functions return, you just
want it to be easy. This is common in application code. Use [thiserror] if you
are a library that wants to design your own dedicated error type(s) so that on
failures the caller gets exactly the information that you choose.
[thiserror]: https://github.com/dtolnay/thiserror
<br>
#### License
<sup>
Licensed under either of <a href="LICENSE-APACHE">Apache License, Version
2.0</a> or <a href="LICENSE-MIT">MIT license</a> at your option.
</sup>
<br>
<sub>
Unless you explicitly state otherwise, any contribution intentionally submitted
for inclusion in this crate by you, as defined in the Apache-2.0 license, shall
be dual licensed as above, without any additional terms or conditions.
</sub>
+194
View File
@@ -0,0 +1,194 @@
#![allow(clippy::uninlined_format_args)]
use std::env;
use std::ffi::OsString;
use std::fs;
use std::io::ErrorKind;
use std::iter;
use std::path::Path;
use std::process::{self, Command, Stdio};
use std::str;
fn main() {
if cfg!(feature = "std") {
println!("cargo:rerun-if-changed=src/nightly.rs");
let error_generic_member_access;
let consider_rustc_bootstrap;
if compile_probe(false) {
// This is a nightly or dev compiler, so it supports unstable
// features regardless of RUSTC_BOOTSTRAP. No need to rerun build
// script if RUSTC_BOOTSTRAP is changed.
error_generic_member_access = true;
consider_rustc_bootstrap = false;
} else if let Some(rustc_bootstrap) = env::var_os("RUSTC_BOOTSTRAP") {
if compile_probe(true) {
// This is a stable or beta compiler for which the user has set
// RUSTC_BOOTSTRAP to turn on unstable features. Rerun build
// script if they change it.
error_generic_member_access = true;
consider_rustc_bootstrap = true;
} else if rustc_bootstrap == "1" {
// This compiler does not support the generic member access API
// in the form that anyhow expects. No need to pay attention to
// RUSTC_BOOTSTRAP.
error_generic_member_access = false;
consider_rustc_bootstrap = false;
} else {
// This is a stable or beta compiler for which RUSTC_BOOTSTRAP
// is set to restrict the use of unstable features by this
// crate.
error_generic_member_access = false;
consider_rustc_bootstrap = true;
}
} else {
// Without RUSTC_BOOTSTRAP, this compiler does not support the
// generic member access API in the form that anyhow expects, but
// try again if the user turns on unstable features.
error_generic_member_access = false;
consider_rustc_bootstrap = true;
}
if error_generic_member_access {
println!("cargo:rustc-cfg=error_generic_member_access");
}
if consider_rustc_bootstrap {
println!("cargo:rerun-if-env-changed=RUSTC_BOOTSTRAP");
}
}
let Some(rustc) = rustc_minor_version() else {
return;
};
if rustc >= 80 {
println!("cargo:rustc-check-cfg=cfg(anyhow_build_probe)");
println!("cargo:rustc-check-cfg=cfg(anyhow_nightly_testing)");
println!("cargo:rustc-check-cfg=cfg(anyhow_no_clippy_format_args)");
println!("cargo:rustc-check-cfg=cfg(anyhow_no_core_error)");
println!("cargo:rustc-check-cfg=cfg(error_generic_member_access)");
}
if rustc < 81 {
// core::error::Error
// https://blog.rust-lang.org/2024/09/05/Rust-1.81.0.html#coreerrorerror
println!("cargo:rustc-cfg=anyhow_no_core_error");
}
if rustc < 85 {
// #[clippy::format_args]
// https://doc.rust-lang.org/1.85.1/clippy/attribs.html#clippyformat_args
println!("cargo:rustc-cfg=anyhow_no_clippy_format_args");
}
}
fn compile_probe(rustc_bootstrap: bool) -> bool {
if env::var_os("RUSTC_STAGE").is_some() {
// We are running inside rustc bootstrap. This is a highly non-standard
// environment with issues such as:
//
// https://github.com/rust-lang/cargo/issues/11138
// https://github.com/rust-lang/rust/issues/114839
//
// Let's just not use nightly features here.
return false;
}
let rustc = cargo_env_var("RUSTC");
let out_dir = cargo_env_var("OUT_DIR");
let out_subdir = Path::new(&out_dir).join("probe");
let probefile = Path::new("src").join("nightly.rs");
if let Err(err) = fs::create_dir(&out_subdir) {
if err.kind() != ErrorKind::AlreadyExists {
eprintln!("Failed to create {}: {}", out_subdir.display(), err);
process::exit(1);
}
}
let rustc_wrapper = env::var_os("RUSTC_WRAPPER").filter(|wrapper| !wrapper.is_empty());
let rustc_workspace_wrapper =
env::var_os("RUSTC_WORKSPACE_WRAPPER").filter(|wrapper| !wrapper.is_empty());
let mut rustc = rustc_wrapper
.into_iter()
.chain(rustc_workspace_wrapper)
.chain(iter::once(rustc));
let mut cmd = Command::new(rustc.next().unwrap());
cmd.args(rustc);
if !rustc_bootstrap {
cmd.env_remove("RUSTC_BOOTSTRAP");
}
cmd.stderr(Stdio::null())
.arg("--cfg=anyhow_build_probe")
.arg("--edition=2018")
.arg("--crate-name=anyhow")
.arg("--crate-type=lib")
.arg("--cap-lints=allow")
.arg("--emit=dep-info,metadata")
.arg("--out-dir")
.arg(&out_subdir)
.arg(probefile);
if let Some(target) = env::var_os("TARGET") {
cmd.arg("--target").arg(target);
}
// If Cargo wants to set RUSTFLAGS, use that.
if let Ok(rustflags) = env::var("CARGO_ENCODED_RUSTFLAGS") {
if !rustflags.is_empty() {
for arg in rustflags.split('\x1f') {
cmd.arg(arg);
}
}
}
let success = match cmd.status() {
Ok(status) => status.success(),
Err(_) => false,
};
// Clean up to avoid leaving nondeterministic absolute paths in the dep-info
// file in OUT_DIR, which causes nonreproducible builds in build systems
// that treat the entire OUT_DIR as an artifact.
if let Err(err) = fs::remove_dir_all(&out_subdir) {
// libc::ENOTEMPTY
// Some filesystems (NFSv3) have timing issues under load where '.nfs*'
// dummy files can continue to get created for a short period after the
// probe command completes, breaking remove_dir_all.
// To be replaced with ErrorKind::DirectoryNotEmpty (Rust 1.83+).
const ENOTEMPTY: i32 = 39;
if !(err.kind() == ErrorKind::NotFound
|| (cfg!(target_os = "linux") && err.raw_os_error() == Some(ENOTEMPTY)))
{
eprintln!("Failed to clean up {}: {}", out_subdir.display(), err);
process::exit(1);
}
}
success
}
fn rustc_minor_version() -> Option<u32> {
let rustc = cargo_env_var("RUSTC");
let output = Command::new(rustc).arg("--version").output().ok()?;
let version = str::from_utf8(&output.stdout).ok()?;
let mut pieces = version.split('.');
if pieces.next() != Some("rustc 1") {
return None;
}
pieces.next()?.parse().ok()
}
fn cargo_env_var(key: &str) -> OsString {
env::var_os(key).unwrap_or_else(|| {
eprintln!(
"Environment variable ${} is not set during execution of build script",
key,
);
process::exit(1);
})
}
+2
View File
@@ -0,0 +1,2 @@
[toolchain]
components = ["rust-src"]
+48
View File
@@ -0,0 +1,48 @@
#[cfg(feature = "std")]
pub(crate) use std::backtrace::Backtrace;
#[cfg(not(feature = "std"))]
pub(crate) enum Backtrace {}
#[cfg(feature = "std")]
macro_rules! backtrace {
() => {
Some(std::backtrace::Backtrace::capture())
};
}
#[cfg(not(feature = "std"))]
macro_rules! backtrace {
() => {
None
};
}
#[cfg(error_generic_member_access)]
macro_rules! backtrace_if_absent {
($err:expr) => {
match $crate::nightly::request_ref_backtrace($err as &dyn core::error::Error) {
Some(_) => None,
None => backtrace!(),
}
};
}
#[cfg(all(not(error_generic_member_access), feature = "std"))]
macro_rules! backtrace_if_absent {
($err:expr) => {
backtrace!()
};
}
#[cfg(all(not(anyhow_no_core_error), not(feature = "std")))]
macro_rules! backtrace_if_absent {
($err:expr) => {
None
};
}
fn _assert_send_sync() {
fn assert<T: Send + Sync>() {}
assert::<Backtrace>();
}
+102
View File
@@ -0,0 +1,102 @@
use self::ChainState::*;
use crate::StdError;
#[cfg(any(feature = "std", not(anyhow_no_core_error)))]
use alloc::vec::{self, Vec};
#[cfg(any(feature = "std", not(anyhow_no_core_error)))]
pub(crate) use crate::Chain;
#[cfg(all(not(feature = "std"), anyhow_no_core_error))]
pub(crate) struct Chain<'a> {
state: ChainState<'a>,
}
#[derive(Clone)]
pub(crate) enum ChainState<'a> {
Linked {
next: Option<&'a (dyn StdError + 'static)>,
},
#[cfg(any(feature = "std", not(anyhow_no_core_error)))]
Buffered {
rest: vec::IntoIter<&'a (dyn StdError + 'static)>,
},
}
impl<'a> Chain<'a> {
#[cold]
pub fn new(head: &'a (dyn StdError + 'static)) -> Self {
Chain {
state: ChainState::Linked { next: Some(head) },
}
}
}
impl<'a> Iterator for Chain<'a> {
type Item = &'a (dyn StdError + 'static);
fn next(&mut self) -> Option<Self::Item> {
match &mut self.state {
Linked { next } => {
let error = (*next)?;
*next = error.source();
Some(error)
}
#[cfg(any(feature = "std", not(anyhow_no_core_error)))]
Buffered { rest } => rest.next(),
}
}
fn size_hint(&self) -> (usize, Option<usize>) {
let len = self.len();
(len, Some(len))
}
}
#[cfg(any(feature = "std", not(anyhow_no_core_error)))]
impl DoubleEndedIterator for Chain<'_> {
fn next_back(&mut self) -> Option<Self::Item> {
match &mut self.state {
Linked { mut next } => {
let mut rest = Vec::new();
while let Some(cause) = next {
next = cause.source();
rest.push(cause);
}
let mut rest = rest.into_iter();
let last = rest.next_back();
self.state = Buffered { rest };
last
}
Buffered { rest } => rest.next_back(),
}
}
}
impl ExactSizeIterator for Chain<'_> {
fn len(&self) -> usize {
match &self.state {
Linked { mut next } => {
let mut len = 0;
while let Some(cause) = next {
next = cause.source();
len += 1;
}
len
}
#[cfg(any(feature = "std", not(anyhow_no_core_error)))]
Buffered { rest } => rest.len(),
}
}
}
#[cfg(any(feature = "std", not(anyhow_no_core_error)))]
impl Default for Chain<'_> {
fn default() -> Self {
Chain {
state: ChainState::Buffered {
rest: Vec::new().into_iter(),
},
}
}
}
+193
View File
@@ -0,0 +1,193 @@
use crate::error::ContextError;
use crate::{Context, Error, StdError};
use core::convert::Infallible;
use core::fmt::{self, Debug, Display, Write};
#[cfg(error_generic_member_access)]
use crate::nightly::{self, Request};
mod ext {
use super::*;
pub trait StdError {
fn ext_context<C>(self, context: C) -> Error
where
C: Display + Send + Sync + 'static;
}
#[cfg(any(feature = "std", not(anyhow_no_core_error)))]
impl<E> StdError for E
where
E: crate::StdError + Send + Sync + 'static,
{
fn ext_context<C>(self, context: C) -> Error
where
C: Display + Send + Sync + 'static,
{
let backtrace = backtrace_if_absent!(&self);
Error::construct_from_context(context, self, backtrace)
}
}
impl StdError for Error {
fn ext_context<C>(self, context: C) -> Error
where
C: Display + Send + Sync + 'static,
{
self.context(context)
}
}
}
impl<T, E> Context<T, E> for Result<T, E>
where
E: ext::StdError + Send + Sync + 'static,
{
fn context<C>(self, context: C) -> Result<T, Error>
where
C: Display + Send + Sync + 'static,
{
// Not using map_err to save 2 useless frames off the captured backtrace
// in ext_context.
match self {
Ok(ok) => Ok(ok),
Err(error) => Err(error.ext_context(context)),
}
}
fn with_context<C, F>(self, context: F) -> Result<T, Error>
where
C: Display + Send + Sync + 'static,
F: FnOnce() -> C,
{
match self {
Ok(ok) => Ok(ok),
Err(error) => Err(error.ext_context(context())),
}
}
}
/// ```
/// # type T = ();
/// #
/// use anyhow::{Context, Result};
///
/// fn maybe_get() -> Option<T> {
/// # const IGNORE: &str = stringify! {
/// ...
/// # };
/// # unimplemented!()
/// }
///
/// fn demo() -> Result<()> {
/// let t = maybe_get().context("there is no T")?;
/// # const IGNORE: &str = stringify! {
/// ...
/// # };
/// # unimplemented!()
/// }
/// ```
impl<T> Context<T, Infallible> for Option<T> {
fn context<C>(self, context: C) -> Result<T, Error>
where
C: Display + Send + Sync + 'static,
{
// Not using ok_or_else to save 2 useless frames off the captured
// backtrace.
match self {
Some(ok) => Ok(ok),
None => Err(Error::construct_from_display(context, backtrace!())),
}
}
fn with_context<C, F>(self, context: F) -> Result<T, Error>
where
C: Display + Send + Sync + 'static,
F: FnOnce() -> C,
{
match self {
Some(ok) => Ok(ok),
None => Err(Error::construct_from_display(context(), backtrace!())),
}
}
}
impl<C, E> Debug for ContextError<C, E>
where
C: Display,
E: Debug,
{
fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
f.debug_struct("Error")
.field("context", &Quoted(&self.context))
.field("source", &self.error)
.finish()
}
}
impl<C, E> Display for ContextError<C, E>
where
C: Display,
{
fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
Display::fmt(&self.context, f)
}
}
impl<C, E> StdError for ContextError<C, E>
where
C: Display,
E: StdError + 'static,
{
fn source(&self) -> Option<&(dyn StdError + 'static)> {
Some(&self.error)
}
#[cfg(error_generic_member_access)]
fn provide<'a>(&'a self, request: &mut Request<'a>) {
nightly::provide(&self.error, request);
}
}
impl<C> StdError for ContextError<C, Error>
where
C: Display,
{
fn source(&self) -> Option<&(dyn StdError + 'static)> {
Some(unsafe { crate::ErrorImpl::error(self.error.inner.by_ref()) })
}
#[cfg(error_generic_member_access)]
fn provide<'a>(&'a self, request: &mut Request<'a>) {
Error::provide(&self.error, request);
}
}
struct Quoted<C>(C);
impl<C> Debug for Quoted<C>
where
C: Display,
{
fn fmt(&self, formatter: &mut fmt::Formatter) -> fmt::Result {
formatter.write_char('"')?;
Quoted(&mut *formatter).write_fmt(format_args!("{}", self.0))?;
formatter.write_char('"')?;
Ok(())
}
}
impl Write for Quoted<&mut fmt::Formatter<'_>> {
fn write_str(&mut self, s: &str) -> fmt::Result {
Display::fmt(&s.escape_debug(), self.0)
}
}
pub(crate) mod private {
use super::*;
pub trait Sealed {}
impl<T, E> Sealed for Result<T, E> where E: ext::StdError {}
impl<T> Sealed for Option<T> {}
}
+935
View File
@@ -0,0 +1,935 @@
use crate::Error;
use alloc::string::String;
use core::fmt::{self, Debug, Write};
use core::mem::MaybeUninit;
use core::ptr;
use core::slice;
use core::str;
#[doc(hidden)]
pub trait BothDebug {
fn __dispatch_ensure(self, msg: &'static str) -> Error;
}
impl<A, B> BothDebug for (A, B)
where
A: Debug,
B: Debug,
{
fn __dispatch_ensure(self, msg: &'static str) -> Error {
render(msg, &self.0, &self.1)
}
}
#[doc(hidden)]
pub trait NotBothDebug {
fn __dispatch_ensure(self, msg: &'static str) -> Error;
}
impl<A, B> NotBothDebug for &(A, B) {
fn __dispatch_ensure(self, msg: &'static str) -> Error {
Error::msg(msg)
}
}
struct Buf {
bytes: [MaybeUninit<u8>; 40],
written: usize,
}
impl Buf {
fn new() -> Self {
Buf {
bytes: [MaybeUninit::uninit(); 40],
written: 0,
}
}
fn as_str(&self) -> &str {
unsafe {
str::from_utf8_unchecked(slice::from_raw_parts(
self.bytes.as_ptr().cast::<u8>(),
self.written,
))
}
}
}
impl Write for Buf {
fn write_str(&mut self, s: &str) -> fmt::Result {
if s.bytes().any(|b| b == b' ' || b == b'\n') {
return Err(fmt::Error);
}
let remaining = self.bytes.len() - self.written;
if s.len() > remaining {
return Err(fmt::Error);
}
unsafe {
ptr::copy_nonoverlapping(
s.as_ptr(),
self.bytes.as_mut_ptr().add(self.written).cast::<u8>(),
s.len(),
);
}
self.written += s.len();
Ok(())
}
}
fn render(msg: &'static str, lhs: &dyn Debug, rhs: &dyn Debug) -> Error {
let mut lhs_buf = Buf::new();
if fmt::write(&mut lhs_buf, format_args!("{:?}", lhs)).is_ok() {
let mut rhs_buf = Buf::new();
if fmt::write(&mut rhs_buf, format_args!("{:?}", rhs)).is_ok() {
let lhs_str = lhs_buf.as_str();
let rhs_str = rhs_buf.as_str();
// "{msg} ({lhs} vs {rhs})"
let len = msg.len() + 2 + lhs_str.len() + 4 + rhs_str.len() + 1;
let mut string = String::with_capacity(len);
string.push_str(msg);
string.push_str(" (");
string.push_str(lhs_str);
string.push_str(" vs ");
string.push_str(rhs_str);
string.push(')');
return Error::msg(string);
}
}
Error::msg(msg)
}
#[doc(hidden)]
#[macro_export]
macro_rules! __parse_ensure {
(atom () $bail:tt $fuel:tt {($($rhs:tt)+) ($($lhs:tt)+) $op:tt} $dup:tt $(,)?) => {
$crate::__fancy_ensure!($($lhs)+, $op, $($rhs)+)
};
// low precedence control flow constructs
(0 $stack:tt ($($bail:tt)*) $fuel:tt $parse:tt $dup:tt return $($rest:tt)*) => {
$crate::__fallback_ensure!($($bail)*)
};
(0 $stack:tt ($($bail:tt)*) $fuel:tt $parse:tt $dup:tt break $($rest:tt)*) => {
$crate::__fallback_ensure!($($bail)*)
};
(0 $stack:tt ($($bail:tt)*) $fuel:tt $parse:tt $dup:tt continue $($rest:tt)*) => {
$crate::__fallback_ensure!($($bail)*)
};
(0 $stack:tt ($($bail:tt)*) $fuel:tt $parse:tt $dup:tt yield $($rest:tt)*) => {
$crate::__fallback_ensure!($($bail)*)
};
(0 $stack:tt ($($bail:tt)*) $fuel:tt $parse:tt $dup:tt move $($rest:tt)*) => {
$crate::__fallback_ensure!($($bail)*)
};
// unary operators
(0 $stack:tt $bail:tt (~$($fuel:tt)*) {($($buf:tt)*) $($parse:tt)*} ($deref:tt $($dup:tt)*) * $($rest:tt)*) => {
$crate::__parse_ensure!(0 $stack $bail ($($fuel)*) {($($buf)* $deref) $($parse)*} ($($rest)*) $($rest)*)
};
(0 $stack:tt $bail:tt (~$($fuel:tt)*) {($($buf:tt)*) $($parse:tt)*} ($not:tt $($dup:tt)*) ! $($rest:tt)*) => {
$crate::__parse_ensure!(0 $stack $bail ($($fuel)*) {($($buf)* $not) $($parse)*} ($($rest)*) $($rest)*)
};
(0 $stack:tt $bail:tt (~$($fuel:tt)*) {($($buf:tt)*) $($parse:tt)*} ($neg:tt $($dup:tt)*) - $($rest:tt)*) => {
$crate::__parse_ensure!(0 $stack $bail ($($fuel)*) {($($buf)* $neg) $($parse)*} ($($rest)*) $($rest)*)
};
(0 $stack:tt $bail:tt (~$($fuel:tt)*) {($($buf:tt)*) $($parse:tt)*} ($let:tt $($dup:tt)*) let $($rest:tt)*) => {
$crate::__parse_ensure!(pat $stack $bail ($($fuel)*) {($($buf)* $let) $($parse)*} ($($rest)*) $($rest)*)
};
(0 $stack:tt $bail:tt (~$($fuel:tt)*) {($($buf:tt)*) $($parse:tt)*} ($lifetime:tt $colon:tt $($dup:tt)*) $label:lifetime : $($rest:tt)*) => {
$crate::__parse_ensure!(0 $stack $bail ($($fuel)*) {($($buf)* $lifetime $colon) $($parse)*} ($($rest)*) $($rest)*)
};
(0 $stack:tt $bail:tt (~$($fuel:tt)*) {($($buf:tt)*) $($parse:tt)*} ($and:tt $mut:tt $($dup:tt)*) &mut $($rest:tt)*) => {
$crate::__parse_ensure!(0 $stack $bail ($($fuel)*) {($($buf)* $and $mut) $($parse)*} ($($rest)*) $($rest)*)
};
(0 $stack:tt $bail:tt (~$($fuel:tt)*) {($($buf:tt)*) $($parse:tt)*} ($and:tt $raw:tt $mut:tt $($dup:tt)*) &raw mut $($rest:tt)*) => {
$crate::__parse_ensure!(0 $stack $bail ($($fuel)*) {($($buf)* $and $raw $mut) $($parse)*} ($($rest)*) $($rest)*)
};
(0 $stack:tt $bail:tt (~$($fuel:tt)*) {($($buf:tt)*) $($parse:tt)*} ($and:tt $raw:tt $const:tt $($dup:tt)*) &raw const $($rest:tt)*) => {
$crate::__parse_ensure!(0 $stack $bail ($($fuel)*) {($($buf)* $and $raw $const) $($parse)*} ($($rest)*) $($rest)*)
};
(0 $stack:tt $bail:tt (~$($fuel:tt)*) {($($buf:tt)*) $($parse:tt)*} ($and:tt $($dup:tt)*) & $($rest:tt)*) => {
$crate::__parse_ensure!(0 $stack $bail ($($fuel)*) {($($buf)* $and) $($parse)*} ($($rest)*) $($rest)*)
};
(0 $stack:tt $bail:tt (~$($fuel:tt)*) {($($buf:tt)*) $($parse:tt)*} ($andand:tt $mut:tt $($dup:tt)*) &&mut $($rest:tt)*) => {
$crate::__parse_ensure!(0 $stack $bail ($($fuel)*) {($($buf)* $andand $mut) $($parse)*} ($($rest)*) $($rest)*)
};
(0 $stack:tt $bail:tt (~$($fuel:tt)*) {($($buf:tt)*) $($parse:tt)*} ($andand:tt $raw:tt $mut:tt $($dup:tt)*) &&raw mut $($rest:tt)*) => {
$crate::__parse_ensure!(0 $stack $bail ($($fuel)*) {($($buf)* $andand $raw $mut) $($parse)*} ($($rest)*) $($rest)*)
};
(0 $stack:tt $bail:tt (~$($fuel:tt)*) {($($buf:tt)*) $($parse:tt)*} ($andand:tt $raw:tt $const:tt $($dup:tt)*) &&raw const $($rest:tt)*) => {
$crate::__parse_ensure!(0 $stack $bail ($($fuel)*) {($($buf)* $andand $raw $const) $($parse)*} ($($rest)*) $($rest)*)
};
(0 $stack:tt $bail:tt (~$($fuel:tt)*) {($($buf:tt)*) $($parse:tt)*} ($andand:tt $($dup:tt)*) && $($rest:tt)*) => {
$crate::__parse_ensure!(0 $stack $bail ($($fuel)*) {($($buf)* $andand) $($parse)*} ($($rest)*) $($rest)*)
};
// control flow constructs
(0 $stack:tt $bail:tt (~$($fuel:tt)*) {($($buf:tt)*) $($parse:tt)*} ($if:tt $($dup:tt)*) if $($rest:tt)*) => {
$crate::__parse_ensure!(0 (cond $stack) $bail ($($fuel)*) {($($buf)* $if) $($parse)*} ($($rest)*) $($rest)*)
};
(0 $stack:tt $bail:tt (~$($fuel:tt)*) {($($buf:tt)*) $($parse:tt)*} ($match:tt $($dup:tt)*) match $($rest:tt)*) => {
$crate::__parse_ensure!(0 (cond $stack) $bail ($($fuel)*) {($($buf)* $match) $($parse)*} ($($rest)*) $($rest)*)
};
(0 $stack:tt $bail:tt (~$($fuel:tt)*) {($($buf:tt)*) $($parse:tt)*} ($while:tt $($dup:tt)*) while $($rest:tt)*) => {
$crate::__parse_ensure!(0 (cond $stack) $bail ($($fuel)*) {($($buf)* $while) $($parse)*} ($($rest)*) $($rest)*)
};
(0 $stack:tt $bail:tt (~$($fuel:tt)*) {($($buf:tt)*) $($parse:tt)*} ($for:tt $($dup:tt)*) for $($rest:tt)*) => {
$crate::__parse_ensure!(pat (cond $stack) $bail ($($fuel)*) {($($buf)* $for) $($parse)*} ($($rest)*) $($rest)*)
};
(atom (cond $stack:tt) $bail:tt (~$($fuel:tt)*) {($($buf:tt)*) $($parse:tt)*} ($brace:tt $($dup:tt)*) {$($block:tt)*} $($rest:tt)*) => {
$crate::__parse_ensure!(cond $stack $bail ($($fuel)*) {($($buf)* $brace) $($parse)*} ($($rest)*) $($rest)*)
};
(cond $stack:tt $bail:tt (~$($fuel:tt)*) {($($buf:tt)*) $($parse:tt)*} ($else:tt $if:tt $($dup:tt)*) else if $($rest:tt)*) => {
$crate::__parse_ensure!(0 (cond $stack) $bail ($($fuel)*) {($($buf)* $else $if) $($parse)*} ($($rest)*) $($rest)*)
};
(cond $stack:tt $bail:tt (~$($fuel:tt)*) {($($buf:tt)*) $($parse:tt)*} ($else:tt $brace:tt $($dup:tt)*) else {$($block:tt)*} $($rest:tt)*) => {
$crate::__parse_ensure!(atom $stack $bail ($($fuel)*) {($($buf)* $else $brace) $($parse)*} ($($rest)*) $($rest)*)
};
(cond $stack:tt $bail:tt (~$($fuel:tt)*) $parse:tt $dup:tt $($rest:tt)*) => {
$crate::__parse_ensure!(atom $stack $bail ($($fuel)*) $parse $dup $($rest)*)
};
// atomic expressions
(0 $stack:tt $bail:tt (~$($fuel:tt)*) {($($buf:tt)*) $($parse:tt)*} ($paren:tt $($dup:tt)*) ($($content:tt)*) $($rest:tt)*) => {
$crate::__parse_ensure!(atom $stack $bail ($($fuel)*) {($($buf)* $paren) $($parse)*} ($($rest)*) $($rest)*)
};
(0 $stack:tt $bail:tt (~$($fuel:tt)*) {($($buf:tt)*) $($parse:tt)*} ($bracket:tt $($dup:tt)*) [$($array:tt)*] $($rest:tt)*) => {
$crate::__parse_ensure!(atom $stack $bail ($($fuel)*) {($($buf)* $bracket) $($parse)*} ($($rest)*) $($rest)*)
};
(0 $stack:tt $bail:tt (~$($fuel:tt)*) {($($buf:tt)*) $($parse:tt)*} ($brace:tt $($dup:tt)*) {$($block:tt)*} $($rest:tt)*) => {
$crate::__parse_ensure!(atom $stack $bail ($($fuel)*) {($($buf)* $brace) $($parse)*} ($($rest)*) $($rest)*)
};
(0 $stack:tt $bail:tt (~$($fuel:tt)*) {($($buf:tt)*) $($parse:tt)*} ($loop:tt $block:tt $($dup:tt)*) loop {$($body:tt)*} $($rest:tt)*) => {
$crate::__parse_ensure!(atom $stack $bail ($($fuel)*) {($($buf)* $loop $block) $($parse)*} ($($rest)*) $($rest)*)
};
(0 $stack:tt $bail:tt (~$($fuel:tt)*) {($($buf:tt)*) $($parse:tt)*} ($async:tt $block:tt $($dup:tt)*) async {$($body:tt)*} $($rest:tt)*) => {
$crate::__parse_ensure!(atom $stack $bail ($($fuel)*) {($($buf)* $async $block) $($parse)*} ($($rest)*) $($rest)*)
};
(0 $stack:tt $bail:tt (~$($fuel:tt)*) {($($buf:tt)*) $($parse:tt)*} ($async:tt $move:tt $block:tt $($dup:tt)*) async move {$($body:tt)*} $($rest:tt)*) => {
$crate::__parse_ensure!(atom $stack $bail ($($fuel)*) {($($buf)* $async $move $block) $($parse)*} ($($rest)*) $($rest)*)
};
(0 $stack:tt $bail:tt (~$($fuel:tt)*) {($($buf:tt)*) $($parse:tt)*} ($unsafe:tt $block:tt $($dup:tt)*) unsafe {$($body:tt)*} $($rest:tt)*) => {
$crate::__parse_ensure!(atom $stack $bail ($($fuel)*) {($($buf)* $unsafe $block) $($parse)*} ($($rest)*) $($rest)*)
};
(0 $stack:tt $bail:tt (~$($fuel:tt)*) {($($buf:tt)*) $($parse:tt)*} ($const:tt $block:tt $($dup:tt)*) const {$($body:tt)*} $($rest:tt)*) => {
// TODO: this is mostly useless due to https://github.com/rust-lang/rust/issues/86730
$crate::__parse_ensure!(atom $stack $bail ($($fuel)*) {($($buf)* $const $block) $($parse)*} ($($rest)*) $($rest)*)
};
(0 $stack:tt $bail:tt (~$($fuel:tt)*) {($($buf:tt)*) $($parse:tt)*} ($literal:tt $($dup:tt)*) $lit:literal $($rest:tt)*) => {
$crate::__parse_ensure!(atom $stack $bail ($($fuel)*) {($($buf)* $literal) $($parse)*} ($($rest)*) $($rest)*)
};
// path expressions
(0 $stack:tt $bail:tt (~$($fuel:tt)*) {($($buf:tt)*) $($parse:tt)*} ($colons:tt $ident:tt $($dup:tt)*) :: $i:ident $($rest:tt)*) => {
$crate::__parse_ensure!(epath (atom $stack) $bail ($($fuel)*) {($($buf)* $colons $ident) $($parse)*} ($($rest)*) $($rest)*)
};
(0 $stack:tt $bail:tt (~$($fuel:tt)*) {($($buf:tt)*) $($parse:tt)*} ($ident:tt $($dup:tt)*) $i:ident $($rest:tt)*) => {
$crate::__parse_ensure!(epath (atom $stack) $bail ($($fuel)*) {($($buf)* $ident) $($parse)*} ($($rest)*) $($rest)*)
};
(0 $stack:tt $bail:tt (~$($fuel:tt)*) {($($buf:tt)*) $($parse:tt)*} ($langle:tt $($dup:tt)*) < $($rest:tt)*) => {
$crate::__parse_ensure!(type (qpath (epath (atom $stack))) $bail ($($fuel)*) {($($buf)* $langle) $($parse)*} ($($rest)*) $($rest)*)
};
(epath $stack:tt $bail:tt (~$($fuel:tt)*) {($($buf:tt)*) $($parse:tt)*} ($colons:tt $langle:tt $($dup:tt)*) :: < $($rest:tt)*) => {
$crate::__parse_ensure!(generic (epath $stack) $bail ($($fuel)*) {($($buf)* $colons $langle) $($parse)*} ($($rest)*) $($rest)*)
};
(epath $stack:tt $bail:tt (~$($fuel:tt)*) {($($buf:tt)*) $($parse:tt)*} ($colons:tt $langle:tt $($dup:tt)*) :: << $($rest:tt)*) => {
$crate::__parse_ensure!(type (qpath (tpath (arglist (epath $stack)))) $bail ($($fuel)*) {($($buf)* $colons $langle) $($parse)*} ($($rest)*) $($rest)*)
};
(epath $stack:tt ($($bail:tt)*) (~$($fuel:tt)*) {($($buf:tt)*) $($parse:tt)*} $dup:tt :: <- - $($rest:tt)*) => {
$crate::__fallback_ensure!($($bail)*)
};
(epath $stack:tt $bail:tt (~$($fuel:tt)*) {($($buf:tt)*) $($parse:tt)*} ($colons:tt $larrow:tt $($dup:tt)*) :: <- $lit:literal $($rest:tt)*) => {
$crate::__parse_ensure!(generic (epath $stack) $bail ($($fuel)*) {($($buf)* $colons $larrow) $($parse)*} ($($dup)*) $($dup)*)
};
(epath $stack:tt $bail:tt (~$($fuel:tt)*) {($($buf:tt)*) $($parse:tt)*} ($colons:tt $ident:tt $($dup:tt)*) :: $i:ident $($rest:tt)*) => {
$crate::__parse_ensure!(epath $stack $bail ($($fuel)*) {($($buf)* $colons $ident) $($parse)*} ($($rest)*) $($rest)*)
};
(epath ($pop:ident $stack:tt) $bail:tt (~$($fuel:tt)*) {($($buf:tt)*) $($parse:tt)*} ($bang:tt $args:tt $($dup:tt)*) ! ($($mac:tt)*) $($rest:tt)*) => {
$crate::__parse_ensure!($pop $stack $bail ($($fuel)*) {($($buf)* $bang $args) $($parse)*} ($($rest)*) $($rest)*)
};
(epath ($pop:ident $stack:tt) $bail:tt (~$($fuel:tt)*) {($($buf:tt)*) $($parse:tt)*} ($bang:tt $args:tt $($dup:tt)*) ! [$($mac:tt)*] $($rest:tt)*) => {
$crate::__parse_ensure!($pop $stack $bail ($($fuel)*) {($($buf)* $bang $args) $($parse)*} ($($rest)*) $($rest)*)
};
(epath ($pop:ident $stack:tt) $bail:tt (~$($fuel:tt)*) {($($buf:tt)*) $($parse:tt)*} ($bang:tt $args:tt $($dup:tt)*) ! {$($mac:tt)*} $($rest:tt)*) => {
$crate::__parse_ensure!($pop $stack $bail ($($fuel)*) {($($buf)* $bang $args) $($parse)*} ($($rest)*) $($rest)*)
};
(epath (split ($pop:ident $stack:tt)) $bail:tt (~$($fuel:tt)*) $parse:tt $dup:tt $($rest:tt)*) => {
$crate::__parse_ensure!($pop (split $stack) $bail ($($fuel)*) $parse $dup $($rest)*)
};
(epath ($pop:ident $stack:tt) $bail:tt (~$($fuel:tt)*) $parse:tt $dup:tt $($rest:tt)*) => {
$crate::__parse_ensure!($pop $stack $bail ($($fuel)*) $parse $dup $($rest)*)
};
// trailer expressions
(atom $stack:tt $bail:tt (~$($fuel:tt)*) {($($buf:tt)*) $($parse:tt)*} ($paren:tt $($dup:tt)*) ($($call:tt)*) $($rest:tt)*) => {
$crate::__parse_ensure!(atom $stack $bail ($($fuel)*) {($($buf)* $paren) $($parse)*} ($($rest)*) $($rest)*)
};
(atom $stack:tt $bail:tt (~$($fuel:tt)*) {($($buf:tt)*) $($parse:tt)*} ($bracket:tt $($dup:tt)*) [$($index:tt)*] $($rest:tt)*) => {
$crate::__parse_ensure!(atom $stack $bail ($($fuel)*) {($($buf)* $bracket) $($parse)*} ($($rest)*) $($rest)*)
};
(atom $stack:tt $bail:tt (~$($fuel:tt)*) {($($buf:tt)*) $($parse:tt)*} ($brace:tt $($dup:tt)*) {$($init:tt)*} $($rest:tt)*) => {
$crate::__parse_ensure!(atom $stack $bail ($($fuel)*) {($($buf)* $brace) $($parse)*} ($($rest)*) $($rest)*)
};
(atom $stack:tt $bail:tt (~$($fuel:tt)*) {($($buf:tt)*) $($parse:tt)*} ($question:tt $($dup:tt)*) ? $($rest:tt)*) => {
$crate::__parse_ensure!(atom $stack $bail ($($fuel)*) {($($buf)* $question) $($parse)*} ($($rest)*) $($rest)*)
};
(atom $stack:tt $bail:tt (~$($fuel:tt)*) {($($buf:tt)*) $($parse:tt)*} ($dot:tt $ident:tt $colons:tt $langle:tt $($dup:tt)*) . $i:ident :: < $($rest:tt)*) => {
$crate::__parse_ensure!(generic (atom $stack) $bail ($($fuel)*) {($($buf)* $dot $ident $colons $langle) $($parse)*} ($($rest)*) $($rest)*)
};
(atom $stack:tt $bail:tt (~$($fuel:tt)*) {($($buf:tt)*) $($parse:tt)*} ($dot:tt $ident:tt $colons:tt $langle:tt $($dup:tt)*) . $i:ident :: << $($rest:tt)*) => {
$crate::__parse_ensure!(type (qpath (tpath (arglist (atom $stack)))) $bail ($($fuel)*) {($($buf)* $dot $ident $colons $langle) $($parse)*} ($($rest)*) $($rest)*)
};
(atom $stack:tt ($($bail:tt)*) (~$($fuel:tt)*) {($($buf:tt)*) $($parse:tt)*} $dup:tt . $i:ident :: <- - $($rest:tt)*) => {
$crate::__fallback_ensure!($($bail)*)
};
(atom $stack:tt $bail:tt (~$($fuel:tt)*) {($($buf:tt)*) $($parse:tt)*} ($dot:tt $ident:tt $colons:tt $larrow:tt $($dup:tt)*) . $i:ident :: <- $lit:literal $($rest:tt)*) => {
$crate::__parse_ensure!(generic (atom $stack) $bail ($($fuel)*) {($($buf)* $dot $ident $colons $larrow) $($parse)*} ($($dup)*) $($dup)*)
};
(atom $stack:tt $bail:tt (~$($fuel:tt)*) {($($buf:tt)*) $($parse:tt)*} ($dot:tt $field:tt $($dup:tt)*) . $i:ident $($rest:tt)*) => {
$crate::__parse_ensure!(atom $stack $bail ($($fuel)*) {($($buf)* $dot $field) $($parse)*} ($($rest)*) $($rest)*)
};
(atom $stack:tt ($($bail:tt)*) (~$($fuel:tt)*) {($($buf:tt)*) $($parse:tt)*} $dup:tt . - $($rest:tt)*) => {
$crate::__fallback_ensure!($($bail)*)
};
(atom $stack:tt $bail:tt (~$($fuel:tt)*) {($($buf:tt)*) $($parse:tt)*} ($dot:tt $index:tt $($dup:tt)*) . $lit:literal $($rest:tt)*) => {
$crate::__parse_ensure!(atom $stack $bail ($($fuel)*) {($($buf)* $dot $index) $($parse)*} ($($rest)*) $($rest)*)
};
(atom $stack:tt $bail:tt (~$($fuel:tt)*) {($($buf:tt)*) $($parse:tt)*} ($as:tt $($dup:tt)*) as $($rest:tt)*) => {
$crate::__parse_ensure!(type (atom $stack) $bail ($($fuel)*) {($($buf)* $as) $($parse)*} ($($rest)*) $($rest)*)
};
// types
(type ($pop:ident $stack:tt) $bail:tt (~$($fuel:tt)*) {($($buf:tt)*) $($parse:tt)*} ($bracket:tt $($dup:tt)*) [$($content:tt)*] $($rest:tt)*) => {
$crate::__parse_ensure!($pop $stack $bail ($($fuel)*) {($($buf)* $bracket) $($parse)*} ($($rest)*) $($rest)*)
};
(type ($pop:ident $stack:tt) $bail:tt (~$($fuel:tt)*) {($($buf:tt)*) $($parse:tt)*} ($paren:tt $($dup:tt)*) ($($content:tt)*) $($rest:tt)*) => {
$crate::__parse_ensure!($pop $stack $bail ($($fuel)*) {($($buf)* $paren) $($parse)*} ($($rest)*) $($rest)*)
};
(type $stack:tt $bail:tt (~$($fuel:tt)*) {($($buf:tt)*) $($parse:tt)*} ($star:tt $const:tt $($dup:tt)*) *const $($rest:tt)*) => {
$crate::__parse_ensure!(type $stack $bail ($($fuel)*) {($($buf)* $star $const) $($parse)*} ($($rest)*) $($rest)*)
};
(type $stack:tt $bail:tt (~$($fuel:tt)*) {($($buf:tt)*) $($parse:tt)*} ($star:tt $mut:tt $($dup:tt)*) *mut $($rest:tt)*) => {
$crate::__parse_ensure!(type $stack $bail ($($fuel)*) {($($buf)* $star $mut) $($parse)*} ($($rest)*) $($rest)*)
};
(type $stack:tt $bail:tt (~$($fuel:tt)*) {($($buf:tt)*) $($parse:tt)*} ($and:tt $lifetime:tt $mut:tt $($dup:tt)*) & $l:lifetime mut $($rest:tt)*) => {
$crate::__parse_ensure!(type $stack $bail ($($fuel)*) {($($buf)* $and $lifetime $mut) $($parse)*} ($($rest)*) $($rest)*)
};
(type $stack:tt $bail:tt (~$($fuel:tt)*) {($($buf:tt)*) $($parse:tt)*} ($and:tt $mut:tt $($dup:tt)*) & mut $($rest:tt)*) => {
$crate::__parse_ensure!(type $stack $bail ($($fuel)*) {($($buf)* $and $mut) $($parse)*} ($($rest)*) $($rest)*)
};
(type $stack:tt $bail:tt (~$($fuel:tt)*) {($($buf:tt)*) $($parse:tt)*} ($and:tt $lifetime:tt $($dup:tt)*) & $l:lifetime $($rest:tt)*) => {
$crate::__parse_ensure!(type $stack $bail ($($fuel)*) {($($buf)* $and $lifetime) $($parse)*} ($($rest)*) $($rest)*)
};
(type $stack:tt $bail:tt (~$($fuel:tt)*) {($($buf:tt)*) $($parse:tt)*} ($and:tt $($dup:tt)*) & $($rest:tt)*) => {
$crate::__parse_ensure!(type $stack $bail ($($fuel)*) {($($buf)* $and) $($parse)*} ($($rest)*) $($rest)*)
};
(type $stack:tt $bail:tt (~$($fuel:tt)*) {($($buf:tt)*) $($parse:tt)*} ($and:tt $lifetime:tt $mut:tt $($dup:tt)*) && $l:lifetime mut $($rest:tt)*) => {
$crate::__parse_ensure!(type $stack $bail ($($fuel)*) {($($buf)* $and $lifetime $mut) $($parse)*} ($($rest)*) $($rest)*)
};
(type $stack:tt $bail:tt (~$($fuel:tt)*) {($($buf:tt)*) $($parse:tt)*} ($and:tt $mut:tt $($dup:tt)*) && mut $($rest:tt)*) => {
$crate::__parse_ensure!(type $stack $bail ($($fuel)*) {($($buf)* $and $mut) $($parse)*} ($($rest)*) $($rest)*)
};
(type $stack:tt $bail:tt (~$($fuel:tt)*) {($($buf:tt)*) $($parse:tt)*} ($and:tt $lifetime:tt $($dup:tt)*) && $l:lifetime $($rest:tt)*) => {
$crate::__parse_ensure!(type $stack $bail ($($fuel)*) {($($buf)* $and $lifetime) $($parse)*} ($($rest)*) $($rest)*)
};
(type $stack:tt $bail:tt (~$($fuel:tt)*) {($($buf:tt)*) $($parse:tt)*} ($and:tt $($dup:tt)*) && $($rest:tt)*) => {
$crate::__parse_ensure!(type $stack $bail ($($fuel)*) {($($buf)* $and) $($parse)*} ($($rest)*) $($rest)*)
};
(type $stack:tt ($($bail:tt)*) (~$($fuel:tt)*) {($($buf:tt)*) $($parse:tt)*} $dup:tt unsafe extern - $($rest:tt)*) => {
$crate::__fallback_ensure!($($bail)*)
};
(type $stack:tt $bail:tt (~$($fuel:tt)*) {($($buf:tt)*) $($parse:tt)*} ($unsafe:tt $(extern $($abi:literal)?)? fn $($dup:tt)*) unsafe $($rest:tt)*) => {
$crate::__parse_ensure!(type $stack $bail ($($fuel)*) {($($buf)* $unsafe) $($parse)*} ($($rest)*) $($rest)*)
};
(type $stack:tt ($($bail:tt)*) (~$($fuel:tt)*) {($($buf:tt)*) $($parse:tt)*} $dup:tt extern - $($rest:tt)*) => {
$crate::__fallback_ensure!($($bail)*)
};
(type $stack:tt $bail:tt (~$($fuel:tt)*) {($($buf:tt)*) $($parse:tt)*} ($extern:tt $abi:tt fn $($dup:tt)*) extern $lit:literal $($rest:tt)*) => {
$crate::__parse_ensure!(type $stack $bail ($($fuel)*) {($($buf)* $extern $abi) $($parse)*} ($($rest)*) $($rest)*)
};
(type $stack:tt $bail:tt (~$($fuel:tt)*) {($($buf:tt)*) $($parse:tt)*} ($extern:tt fn $($dup:tt)*) extern $($rest:tt)*) => {
$crate::__parse_ensure!(type $stack $bail ($($fuel)*) {($($buf)* $extern) $($parse)*} ($($rest)*) $($rest)*)
};
(type $stack:tt $bail:tt (~$($fuel:tt)*) {($($buf:tt)*) $($parse:tt)*} ($fn:tt $paren:tt $arrow:tt $($dup:tt)*) fn ($($args:tt)*) -> $($rest:tt)*) => {
$crate::__parse_ensure!(type $stack $bail ($($fuel)*) {($($buf)* $fn $paren $arrow) $($parse)*} ($($rest)*) $($rest)*)
};
(type ($pop:ident $stack:tt) $bail:tt (~$($fuel:tt)*) {($($buf:tt)*) $($parse:tt)*} ($fn:tt $paren:tt $($dup:tt)*) fn ($($args:tt)*) $($rest:tt)*) => {
$crate::__parse_ensure!($pop $stack $bail ($($fuel)*) {($($buf)* $fn $paren) $($parse)*} ($($rest)*) $($rest)*)
};
(type $stack:tt $bail:tt (~$($fuel:tt)*) {($($buf:tt)*) $($parse:tt)*} ($impl:tt $($dup:tt)*) impl $($rest:tt)*) => {
$crate::__parse_ensure!(type $stack $bail ($($fuel)*) {($($buf)* $impl) $($parse)*} ($($rest)*) $($rest)*)
};
(type $stack:tt $bail:tt (~$($fuel:tt)*) {($($buf:tt)*) $($parse:tt)*} ($dyn:tt $($dup:tt)*) dyn $($rest:tt)*) => {
$crate::__parse_ensure!(type $stack $bail ($($fuel)*) {($($buf)* $dyn) $($parse)*} ($($rest)*) $($rest)*)
};
(type ($pop:ident $stack:tt) $bail:tt (~$($fuel:tt)*) {($($buf:tt)*) $($parse:tt)*} ($wild:tt $($dup:tt)*) _ $($rest:tt)*) => {
$crate::__parse_ensure!($pop $stack $bail ($($fuel)*) {($($buf)* $wild) $($parse)*} ($($rest)*) $($rest)*)
};
(type ($pop:ident $stack:tt) $bail:tt (~$($fuel:tt)*) {($($buf:tt)*) $($parse:tt)*} ($never:tt $($dup:tt)*) ! $($rest:tt)*) => {
$crate::__parse_ensure!($pop $stack $bail ($($fuel)*) {($($buf)* $never) $($parse)*} ($($rest)*) $($rest)*)
};
(type $stack:tt $bail:tt (~$($fuel:tt)*) {($($buf:tt)*) $($parse:tt)*} ($for:tt $langle:tt $($dup:tt)*) for < $($rest:tt)*) => {
$crate::__parse_ensure!(generic (type $stack) $bail ($($fuel)*) {($($buf)* $for $langle) $($parse)*} ($($rest)*) $($rest)*)
};
// path types
(type $stack:tt $bail:tt (~$($fuel:tt)*) {($($buf:tt)*) $($parse:tt)*} ($colons:tt $ident:tt $($dup:tt)*) :: $i:ident $($rest:tt)*) => {
$crate::__parse_ensure!(tpath $stack $bail ($($fuel)*) {($($buf)* $colons $ident) $($parse)*} ($($rest)*) $($rest)*)
};
(type $stack:tt $bail:tt (~$($fuel:tt)*) {($($buf:tt)*) $($parse:tt)*} ($ident:tt $($dup:tt)*) $i:ident $($rest:tt)*) => {
$crate::__parse_ensure!(tpath $stack $bail ($($fuel)*) {($($buf)* $ident) $($parse)*} ($($rest)*) $($rest)*)
};
(type $stack:tt $bail:tt (~$($fuel:tt)*) {($($buf:tt)*) $($parse:tt)*} ($langle:tt $($dup:tt)*) < $($rest:tt)*) => {
$crate::__parse_ensure!(type (qpath (tpath $stack)) $bail ($($fuel)*) {($($buf)* $langle) $($parse)*} ($($rest)*) $($rest)*)
};
(tpath $stack:tt $bail:tt (~$($fuel:tt)*) {($($buf:tt)*) $($parse:tt)*} ($langle:tt $($dup:tt)*) < $($rest:tt)*) => {
$crate::__parse_ensure!(generic (tpath $stack) $bail ($($fuel)*) {($($buf)* $langle) $($parse)*} ($($rest)*) $($rest)*)
};
(tpath $stack:tt $bail:tt (~$($fuel:tt)*) {($($buf:tt)*) $($parse:tt)*} ($langle:tt $($dup:tt)*) << $($rest:tt)*) => {
$crate::__parse_ensure!(type (qpath (tpath (arglist (tpath $stack)))) $bail ($($fuel)*) {($($buf)* $langle) $($parse)*} ($($rest)*) $($rest)*)
};
(tpath $stack:tt ($($bail:tt)*) (~$($fuel:tt)*) {($($buf:tt)*) $($parse:tt)*} $dup:tt <- - $($rest:tt)*) => {
$crate::__fallback_ensure!($($bail)*)
};
(tpath $stack:tt $bail:tt (~$($fuel:tt)*) {($($buf:tt)*) $($parse:tt)*} ($larrow:tt $($dup:tt)*) <- $lit:literal $($rest:tt)*) => {
$crate::__parse_ensure!(generic (tpath $stack) $bail ($($fuel)*) {($($buf)* $larrow) $($parse)*} ($($dup)*) $($dup)*)
};
(tpath $stack:tt $bail:tt (~$($fuel:tt)*) {($($buf:tt)*) $($parse:tt)*} ($colons:tt $langle:tt $($dup:tt)*) :: < $($rest:tt)*) => {
$crate::__parse_ensure!(generic (tpath $stack) $bail ($($fuel)*) {($($buf)* $colons $langle) $($parse)*} ($($rest)*) $($rest)*)
};
(tpath $stack:tt $bail:tt (~$($fuel:tt)*) {($($buf:tt)*) $($parse:tt)*} ($colons:tt $langle:tt $($dup:tt)*) :: << $($rest:tt)*) => {
$crate::__parse_ensure!(type (qpath (tpath (arglist (tpath $stack)))) $bail ($($fuel)*) {($($buf)* $colons $langle) $($parse)*} ($($rest)*) $($rest)*)
};
(tpath $stack:tt ($($bail:tt)*) (~$($fuel:tt)*) {($($buf:tt)*) $($parse:tt)*} $dup:tt :: <- - $($rest:tt)*) => {
$crate::__fallback_ensure!($($bail)*)
};
(tpath $stack:tt $bail:tt (~$($fuel:tt)*) {($($buf:tt)*) $($parse:tt)*} ($colons:tt $larrow:tt $($dup:tt)*) :: <- $lit:literal $($rest:tt)*) => {
$crate::__parse_ensure!(generic (tpath $stack) $bail ($($fuel)*) {($($buf)* $colons $larrow) $($parse)*} ($($dup)*) $($dup)*)
};
(tpath $stack:tt $bail:tt (~$($fuel:tt)*) {($($buf:tt)*) $($parse:tt)*} ($colons:tt $ident:tt $($dup:tt)*) :: $i:ident $($rest:tt)*) => {
$crate::__parse_ensure!(tpath $stack $bail ($($fuel)*) {($($buf)* $colons $ident) $($parse)*} ($($rest)*) $($rest)*)
};
(tpath $stack:tt $bail:tt (~$($fuel:tt)*) {($($buf:tt)*) $($parse:tt)*} ($paren:tt $arrow:tt $($dup:tt)*) ($($args:tt)*) -> $($rest:tt)*) => {
$crate::__parse_ensure!(type $stack $bail ($($fuel)*) {($($buf)* $paren $arrow) $($parse)*} ($($rest)*) $($rest)*)
};
(tpath $stack:tt $bail:tt (~$($fuel:tt)*) {($($buf:tt)*) $($parse:tt)*} ($paren:tt $($dup:tt)*) ($($args:tt)*) $($rest:tt)*) => {
$crate::__parse_ensure!(object $stack $bail ($($fuel)*) {($($buf)* $paren) $($parse)*} ($($rest)*) $($rest)*)
};
(tpath $stack:tt $bail:tt (~$($fuel:tt)*) {($($buf:tt)*) $($parse:tt)*} ($colons:tt $paren:tt $arrow:tt $($dup:tt)*) :: ($($args:tt)*) -> $($rest:tt)*) => {
$crate::__parse_ensure!(type $stack $bail ($($fuel)*) {($($buf)* $colons $paren $arrow) $($parse)*} ($($rest)*) $($rest)*)
};
(tpath $stack:tt $bail:tt (~$($fuel:tt)*) {($($buf:tt)*) $($parse:tt)*} ($colons:tt $paren:tt $($dup:tt)*) :: ($($args:tt)*) $($rest:tt)*) => {
$crate::__parse_ensure!(object $stack $bail ($($fuel)*) {($($buf)* $colons $paren) $($parse)*} ($($rest)*) $($rest)*)
};
(tpath ($pop:ident $stack:tt) $bail:tt (~$($fuel:tt)*) {($($buf:tt)*) $($parse:tt)*} ($bang:tt $args:tt $($dup:tt)*) ! ($($mac:tt)*) $($rest:tt)*) => {
$crate::__parse_ensure!($pop $stack $bail ($($fuel)*) {($($buf)* $bang $args) $($parse)*} ($($rest)*) $($rest)*)
};
(tpath ($pop:ident $stack:tt) $bail:tt (~$($fuel:tt)*) {($($buf:tt)*) $($parse:tt)*} ($bang:tt $args:tt $($dup:tt)*) ! [$($mac:tt)*] $($rest:tt)*) => {
$crate::__parse_ensure!($pop $stack $bail ($($fuel)*) {($($buf)* $bang $args) $($parse)*} ($($rest)*) $($rest)*)
};
(tpath ($pop:ident $stack:tt) $bail:tt (~$($fuel:tt)*) {($($buf:tt)*) $($parse:tt)*} ($bang:tt $args:tt $($dup:tt)*) ! {$($mac:tt)*} $($rest:tt)*) => {
$crate::__parse_ensure!($pop $stack $bail ($($fuel)*) {($($buf)* $bang $args) $($parse)*} ($($rest)*) $($rest)*)
};
(tpath $stack:tt $bail:tt (~$($fuel:tt)*) $parse:tt $dup:tt $($rest:tt)*) => {
$crate::__parse_ensure!(object $stack $bail ($($fuel)*) $parse $dup $($rest)*)
};
// qualified paths
(qpath (split ($pop:ident $stack:tt)) $bail:tt (~$($fuel:tt)*) {($($buf:tt)*) $($parse:tt)*} ($rangle:tt $colons:tt $ident:tt $($dup:tt)*) >> :: $i:ident $($rest:tt)*) => {
$crate::__parse_ensure!($pop $stack $bail ($($fuel)*) {($($buf)* $rangle $colons $ident) $($parse)*} ($($rest)*) $($rest)*)
};
(qpath ($pop:ident $stack:tt) $bail:tt (~$($fuel:tt)*) {($($buf:tt)*) $($parse:tt)*} ($rangle:tt $colons:tt $ident:tt $($dup:tt)*) > :: $i:ident $($rest:tt)*) => {
$crate::__parse_ensure!($pop $stack $bail ($($fuel)*) {($($buf)* $rangle $colons $ident) $($parse)*} ($($rest)*) $($rest)*)
};
(qpath $stack:tt $bail:tt (~$($fuel:tt)*) {($($buf:tt)*) $($parse:tt)*} ($as:tt $($dup:tt)*) as $($rest:tt)*) => {
$crate::__parse_ensure!(type (qpath $stack) $bail ($($fuel)*) {($($buf)* $as) $($parse)*} ($($rest)*) $($rest)*)
};
// trait objects
(object (arglist $stack:tt) $bail:tt (~$($fuel:tt)*) {($($buf:tt)*) $($parse:tt)*} ($plus:tt $colons:tt $ident:tt $($dup:tt)*) + :: $i:ident $($rest:tt)*) => {
$crate::__parse_ensure!(tpath (arglist $stack) $bail ($($fuel)*) {($($buf)* $plus $colons $ident) $($parse)*} ($($rest)*) $($rest)*)
};
(object (arglist $stack:tt) $bail:tt (~$($fuel:tt)*) {($($buf:tt)*) $($parse:tt)*} ($plus:tt $ident:tt $($dup:tt)*) + $i:ident $($rest:tt)*) => {
$crate::__parse_ensure!(tpath (arglist $stack) $bail ($($fuel)*) {($($buf)* $plus $ident) $($parse)*} ($($rest)*) $($rest)*)
};
(object (split ($pop:ident $stack:tt)) $bail:tt (~$($fuel:tt)*) $parse:tt $dup:tt $($rest:tt)*) => {
$crate::__parse_ensure!($pop (split $stack) $bail ($($fuel)*) $parse $dup $($rest)*)
};
(object ($pop:ident $stack:tt) $bail:tt (~$($fuel:tt)*) $parse:tt $dup:tt $($rest:tt)*) => {
$crate::__parse_ensure!($pop $stack $bail ($($fuel)*) $parse $dup $($rest)*)
};
// angle bracketed generic arguments
(generic (split ($pop:ident $stack:tt)) $bail:tt (~$($fuel:tt)*) {($($buf:tt)*) $($parse:tt)*} ($rangle:tt $($dup:tt)*) >> $($rest:tt)*) => {
$crate::__parse_ensure!($pop $stack $bail ($($fuel)*) {($($buf)* $rangle) $($parse)*} ($($rest)*) $($rest)*)
};
(generic ($pop:ident $stack:tt) $bail:tt (~$($fuel:tt)*) {($($buf:tt)*) $($parse:tt)*} ($rangle:tt $($dup:tt)*) > $($rest:tt)*) => {
$crate::__parse_ensure!($pop $stack $bail ($($fuel)*) {($($buf)* $rangle) $($parse)*} ($($rest)*) $($rest)*)
};
(generic ($pop:ident $stack:tt) $bail:tt (~$($fuel:tt)*) {($($buf:tt)*) $($parse:tt)*} ($rangle:tt $($dup:tt)*) >> $($rest:tt)*) => {
$crate::__parse_ensure!($pop (split $stack) $bail ($($fuel)*) {($($buf)*) $($parse)*} ($rangle $($rest)*) $rangle $($rest)*)
};
(generic $stack:tt ($($bail:tt)*) (~$($fuel:tt)*) {($($buf:tt)*) $($parse:tt)*} $dup:tt - - $($rest:tt)*) => {
$crate::__fallback_ensure!($($bail)*)
};
(generic $stack:tt $bail:tt (~$($fuel:tt)*) {($($buf:tt)*) $($parse:tt)*} ($neg:tt $($dup:tt)*) - $lit:literal $($rest:tt)*) => {
$crate::__parse_ensure!(generic $stack $bail ($($fuel)*) {($($buf)* $neg) $($parse)*} ($($dup)*) $($dup)*)
};
(generic $stack:tt ($($bail:tt)*) (~$($fuel:tt)*) {($($buf:tt)*) $($parse:tt)*} $dup:tt - $($rest:tt)*) => {
$crate::__fallback_ensure!($($bail)*)
};
(generic $stack:tt $bail:tt (~$($fuel:tt)*) {($($buf:tt)*) $($parse:tt)*} ($literal:tt $($dup:tt)*) $lit:literal $($rest:tt)*) => {
$crate::__parse_ensure!(arglist $stack $bail ($($fuel)*) {($($buf)* $literal) $($parse)*} ($($rest)*) $($rest)*)
};
(generic $stack:tt $bail:tt (~$($fuel:tt)*) {($($buf:tt)*) $($parse:tt)*} ($brace:tt $($dup:tt)*) {$($block:tt)*} $($rest:tt)*) => {
$crate::__parse_ensure!(arglist $stack $bail ($($fuel)*) {($($buf)* $brace) $($parse)*} ($($rest)*) $($rest)*)
};
(generic $stack:tt $bail:tt (~$($fuel:tt)*) {($($buf:tt)*) $($parse:tt)*} ($lifetime:tt $($dup:tt)*) $l:lifetime $($rest:tt)*) => {
$crate::__parse_ensure!(arglist $stack $bail ($($fuel)*) {($($buf)* $lifetime) $($parse)*} ($($rest)*) $($rest)*)
};
(generic $stack:tt $bail:tt (~$($fuel:tt)*) {($($buf:tt)*) $($parse:tt)*} ($assoc:tt $eq:tt $($dup:tt)*) $ident:ident = $($rest:tt)*) => {
$crate::__parse_ensure!(type (arglist $stack) $bail ($($fuel)*) {($($buf)* $assoc $eq) $($parse)*} ($($rest)*) $($rest)*)
};
(generic $stack:tt $bail:tt (~$($fuel:tt)*) $parse:tt $dup:tt $($rest:tt)*) => {
$crate::__parse_ensure!(type (arglist $stack) $bail ($($fuel)*) $parse $dup $($rest)*)
};
(arglist $stack:tt $bail:tt (~$($fuel:tt)*) {($($buf:tt)*) $($parse:tt)*} ($comma:tt $($dup:tt)*) , $($rest:tt)*) => {
$crate::__parse_ensure!(generic $stack $bail ($($fuel)*) {($($buf)* $comma) $($parse)*} ($($rest)*) $($rest)*)
};
(arglist (split ($pop:ident $stack:tt)) $bail:tt (~$($fuel:tt)*) {($($buf:tt)*) $($parse:tt)*} ($rangle:tt $($dup:tt)*) >> $($rest:tt)*) => {
$crate::__parse_ensure!($pop $stack $bail ($($fuel)*) {($($buf)*) $rangle $($parse)*} ($($rest)*) $($rest)*)
};
(arglist ($pop:ident $stack:tt) $bail:tt (~$($fuel:tt)*) {($($buf:tt)*) $($parse:tt)*} ($rangle:tt $($dup:tt)*) > $($rest:tt)*) => {
$crate::__parse_ensure!($pop $stack $bail ($($fuel)*) {($($buf)* $rangle) $($parse)*} ($($rest)*) $($rest)*)
};
(arglist ($pop:ident $stack:tt) $bail:tt (~$($fuel:tt)*) {($($buf:tt)*) $($parse:tt)*} ($rangle:tt $($dup:tt)*) >> $($rest:tt)*) => {
$crate::__parse_ensure!($pop (split $stack) $bail ($($fuel)*) {($($buf)*) $($parse)*} ($rangle $($rest)*) $rangle $($rest)*)
};
// patterns
(pat $stack:tt $bail:tt (~$($fuel:tt)*) {($($buf:tt)*) $($parse:tt)*} ($pipe:tt $($dup:tt)*) | $($rest:tt)*) => {
$crate::__parse_ensure!(pat $stack $bail ($($fuel)*) {($($buf)* $pipe) $($parse)*} ($($rest)*) $($rest)*)
};
(pat $stack:tt $bail:tt (~$($fuel:tt)*) {($($buf:tt)*) $($parse:tt)*} ($eq:tt $($dup:tt)*) = $($rest:tt)*) => {
$crate::__parse_ensure!(0 $stack $bail ($($fuel)*) {($($buf)* $eq) $($parse)*} ($($rest)*) $($rest)*)
};
(pat $stack:tt $bail:tt (~$($fuel:tt)*) {($($buf:tt)*) $($parse:tt)*} ($in:tt $($dup:tt)*) in $($rest:tt)*) => {
$crate::__parse_ensure!(0 $stack $bail ($($fuel)*) {($($buf)* $in) $($parse)*} ($($rest)*) $($rest)*)
};
(pat $stack:tt $bail:tt (~$($fuel:tt)*) {($($buf:tt)*) $($parse:tt)*} ($ref:tt $($dup:tt)*) ref $($rest:tt)*) => {
$crate::__parse_ensure!(pat $stack $bail ($($fuel)*) {($($buf)* $ref) $($parse)*} ($($rest)*) $($rest)*)
};
(pat $stack:tt $bail:tt (~$($fuel:tt)*) {($($buf:tt)*) $($parse:tt)*} ($mut:tt $($dup:tt)*) mut $($rest:tt)*) => {
$crate::__parse_ensure!(pat $stack $bail ($($fuel)*) {($($buf)* $mut) $($parse)*} ($($rest)*) $($rest)*)
};
(pat $stack:tt $bail:tt (~$($fuel:tt)*) {($($buf:tt)*) $($parse:tt)*} ($at:tt $($dup:tt)*) @ $($rest:tt)*) => {
$crate::__parse_ensure!(pat $stack $bail ($($fuel)*) {($($buf)* $at) $($parse)*} ($($rest)*) $($rest)*)
};
(pat $stack:tt ($($bail:tt)*) (~$($fuel:tt)*) {($($buf:tt)*) $($parse:tt)*} $dup:tt - - $($rest:tt)*) => {
$crate::__fallback_ensure!($($bail)*)
};
(pat $stack:tt $bail:tt (~$($fuel:tt)*) {($($buf:tt)*) $($parse:tt)*} ($neg:tt $($dup:tt)*) - $lit:literal $($rest:tt)*) => {
$crate::__parse_ensure!(pat $stack $bail ($($fuel)*) {($($buf)* $neg) $($parse)*} ($($dup)*) $($dup)*)
};
(pat $stack:tt ($($bail:tt)*) (~$($fuel:tt)*) {($($buf:tt)*) $($parse:tt)*} $dup:tt - $($rest:tt)*) => {
$crate::__fallback_ensure!($($bail)*)
};
(pat $stack:tt $bail:tt (~$($fuel:tt)*) {($($buf:tt)*) $($parse:tt)*} ($literal:tt $($dup:tt)*) $lit:literal $($rest:tt)*) => {
$crate::__parse_ensure!(pat $stack $bail ($($fuel)*) {($($buf)* $literal) $($parse)*} ($($rest)*) $($rest)*)
};
(pat $stack:tt $bail:tt (~$($fuel:tt)*) {($($buf:tt)*) $($parse:tt)*} ($range:tt $($dup:tt)*) .. $($rest:tt)*) => {
$crate::__parse_ensure!(pat $stack $bail ($($fuel)*) {($($buf)* $range) $($parse)*} ($($rest)*) $($rest)*)
};
(pat $stack:tt $bail:tt (~$($fuel:tt)*) {($($buf:tt)*) $($parse:tt)*} ($range:tt $($dup:tt)*) ..= $($rest:tt)*) => {
$crate::__parse_ensure!(pat $stack $bail ($($fuel)*) {($($buf)* $range) $($parse)*} ($($rest)*) $($rest)*)
};
(pat $stack:tt $bail:tt (~$($fuel:tt)*) {($($buf:tt)*) $($parse:tt)*} ($and:tt $($dup:tt)*) & $($rest:tt)*) => {
$crate::__parse_ensure!(pat $stack $bail ($($fuel)*) {($($buf)* $and) $($parse)*} ($($rest)*) $($rest)*)
};
(pat $stack:tt $bail:tt (~$($fuel:tt)*) {($($buf:tt)*) $($parse:tt)*} ($andand:tt $($dup:tt)*) && $($rest:tt)*) => {
$crate::__parse_ensure!(pat $stack $bail ($($fuel)*) {($($buf)* $andand) $($parse)*} ($($rest)*) $($rest)*)
};
(pat $stack:tt $bail:tt (~$($fuel:tt)*) {($($buf:tt)*) $($parse:tt)*} ($paren:tt $($dup:tt)*) ($($content:tt)*) $($rest:tt)*) => {
$crate::__parse_ensure!(pat $stack $bail ($($fuel)*) {($($buf)* $paren) $($parse)*} ($($rest)*) $($rest)*)
};
(pat $stack:tt $bail:tt (~$($fuel:tt)*) {($($buf:tt)*) $($parse:tt)*} ($bracket:tt $($dup:tt)*) [$($content:tt)*] $($rest:tt)*) => {
$crate::__parse_ensure!(pat $stack $bail ($($fuel)*) {($($buf)* $bracket) $($parse)*} ($($rest)*) $($rest)*)
};
(pat $stack:tt $bail:tt (~$($fuel:tt)*) {($($buf:tt)*) $($parse:tt)*} ($brace:tt $($dup:tt)*) {$($content:tt)*} $($rest:tt)*) => {
$crate::__parse_ensure!(pat $stack $bail ($($fuel)*) {($($buf)* $brace) $($parse)*} ($($rest)*) $($rest)*)
};
(pat $stack:tt $bail:tt (~$($fuel:tt)*) {($($buf:tt)*) $($parse:tt)*} ($wild:tt $($dup:tt)*) _ $($rest:tt)*) => {
$crate::__parse_ensure!(pat $stack $bail ($($fuel)*) {($($buf)* $wild) $($parse)*} ($($rest)*) $($rest)*)
};
(pat $stack:tt $bail:tt (~$($fuel:tt)*) {($($buf:tt)*) $($parse:tt)*} ($colons:tt $ident:tt $($dup:tt)*) :: $i:ident $($rest:tt)*) => {
$crate::__parse_ensure!(epath (pat $stack) $bail ($($fuel)*) {($($buf)* $colons $ident) $($parse)*} ($($rest)*) $($rest)*)
};
(pat $stack:tt $bail:tt (~$($fuel:tt)*) {($($buf:tt)*) $($parse:tt)*} ($ident:tt $($dup:tt)*) $i:ident $($rest:tt)*) => {
$crate::__parse_ensure!(epath (pat $stack) $bail ($($fuel)*) {($($buf)* $ident) $($parse)*} ($($rest)*) $($rest)*)
};
(pat $stack:tt $bail:tt (~$($fuel:tt)*) {($($buf:tt)*) $($parse:tt)*} ($langle:tt $($dup:tt)*) < $($rest:tt)*) => {
$crate::__parse_ensure!(type (qpath (epath (pat $stack))) $bail ($($fuel)*) {($($buf)* $langle) $($parse)*} ($($rest)*) $($rest)*)
};
// comparison binary operators
(atom () $bail:tt (~$($fuel:tt)*) {($($buf:tt)*) $($parse:tt)*} ($eq:tt $($dup:tt)*) == $($rest:tt)*) => {
$crate::__parse_ensure!(0 () $bail ($($fuel)*) {() $($parse)* ($($buf)*) $eq} ($($rest)*) $($rest)*)
};
(atom $stack:tt $bail:tt (~$($fuel:tt)*) {($($buf:tt)+) $($parse:tt)*} ($eq:tt $($dup:tt)*) == $($rest:tt)*) => {
$crate::__parse_ensure!(0 $stack $bail ($($fuel)*) {($($buf)* $eq) $($parse)*} ($($rest)*) $($rest)*)
};
(atom () $bail:tt (~$($fuel:tt)*) {($($buf:tt)*) $($parse:tt)*} ($le:tt $($dup:tt)*) <= $($rest:tt)*) => {
$crate::__parse_ensure!(0 () $bail ($($fuel)*) {() $($parse)* ($($buf)*) $le} ($($rest)*) $($rest)*)
};
(atom $stack:tt $bail:tt (~$($fuel:tt)*) {($($buf:tt)+) $($parse:tt)*} ($le:tt $($dup:tt)*) <= $($rest:tt)*) => {
$crate::__parse_ensure!(0 $stack $bail ($($fuel)*) {($($buf)* $le) $($parse)*} ($($rest)*) $($rest)*)
};
(atom () $bail:tt (~$($fuel:tt)*) {($($buf:tt)*) $($parse:tt)*} ($lt:tt $($dup:tt)*) < $($rest:tt)*) => {
$crate::__parse_ensure!(0 () $bail ($($fuel)*) {() $($parse)* ($($buf)*) $lt} ($($rest)*) $($rest)*)
};
(atom $stack:tt $bail:tt (~$($fuel:tt)*) {($($buf:tt)+) $($parse:tt)*} ($lt:tt $($dup:tt)*) < $($rest:tt)*) => {
$crate::__parse_ensure!(0 $stack $bail ($($fuel)*) {($($buf)* $lt) $($parse)*} ($($rest)*) $($rest)*)
};
(atom () $bail:tt (~$($fuel:tt)*) {($($buf:tt)*) $($parse:tt)*} ($ne:tt $($dup:tt)*) != $($rest:tt)*) => {
$crate::__parse_ensure!(0 () $bail ($($fuel)*) {() $($parse)* ($($buf)*) $ne} ($($rest)*) $($rest)*)
};
(atom $stack:tt $bail:tt (~$($fuel:tt)*) {($($buf:tt)+) $($parse:tt)*} ($ne:tt $($dup:tt)*) != $($rest:tt)*) => {
$crate::__parse_ensure!(0 $stack $bail ($($fuel)*) {($($buf)* $ne) $($parse)*} ($($rest)*) $($rest)*)
};
(atom () $bail:tt (~$($fuel:tt)*) {($($buf:tt)*) $($parse:tt)*} ($ge:tt $($dup:tt)*) >= $($rest:tt)*) => {
$crate::__parse_ensure!(0 () $bail ($($fuel)*) {() $($parse)* ($($buf)*) $ge} ($($rest)*) $($rest)*)
};
(atom $stack:tt $bail:tt (~$($fuel:tt)*) {($($buf:tt)+) $($parse:tt)*} ($ge:tt $($dup:tt)*) >= $($rest:tt)*) => {
$crate::__parse_ensure!(0 $stack $bail ($($fuel)*) {($($buf)* $ge) $($parse)*} ($($rest)*) $($rest)*)
};
(atom (split ()) $bail:tt (~$($fuel:tt)*) {($($buf:tt)*) $($parse:tt)*} $dup:tt >> $($rest:tt)*) => {
$crate::__parse_ensure!(0 () $bail ($($fuel)*) {() $($parse)* ($($buf)* > ) > } ($($rest)*) $($rest)*)
};
(atom () $bail:tt (~$($fuel:tt)*) {($($buf:tt)*) $($parse:tt)*} ($gt:tt $($dup:tt)*) > $($rest:tt)*) => {
$crate::__parse_ensure!(0 () $bail ($($fuel)*) {() $($parse)* ($($buf)*) $gt} ($($rest)*) $($rest)*)
};
(atom (split $stack:tt) $bail:tt (~$($fuel:tt)*) {($($buf:tt)+) $($parse:tt)*} ($rangle:tt $($dup:tt)*) >> $($rest:tt)*) => {
$crate::__parse_ensure!(0 $stack $bail ($($fuel)*) {($($buf)* $rangle) $($parse)*} ($($rest)*) $($rest)*)
};
(atom $stack:tt $bail:tt (~$($fuel:tt)*) {($($buf:tt)+) $($parse:tt)*} ($gt:tt $($dup:tt)*) > $($rest:tt)*) => {
$crate::__parse_ensure!(0 $stack $bail ($($fuel)*) {($($buf)* $gt) $($parse)*} ($($rest)*) $($rest)*)
};
// high precedence binary operators
(atom $stack:tt $bail:tt (~$($fuel:tt)*) {($($buf:tt)*) $($parse:tt)*} ($add:tt $($dup:tt)*) + $($rest:tt)*) => {
$crate::__parse_ensure!(0 $stack $bail ($($fuel)*) {($($buf)* $add) $($parse)*} ($($rest)*) $($rest)*)
};
(atom $stack:tt $bail:tt (~$($fuel:tt)*) {($($buf:tt)*) $($parse:tt)*} ($sub:tt $($dup:tt)*) - $($rest:tt)*) => {
$crate::__parse_ensure!(0 $stack $bail ($($fuel)*) {($($buf)* $sub) $($parse)*} ($($rest)*) $($rest)*)
};
(atom $stack:tt $bail:tt (~$($fuel:tt)*) {($($buf:tt)*) $($parse:tt)*} ($mul:tt $($dup:tt)*) * $($rest:tt)*) => {
$crate::__parse_ensure!(0 $stack $bail ($($fuel)*) {($($buf)* $mul) $($parse)*} ($($rest)*) $($rest)*)
};
(atom $stack:tt $bail:tt (~$($fuel:tt)*) {($($buf:tt)*) $($parse:tt)*} ($div:tt $($dup:tt)*) / $($rest:tt)*) => {
$crate::__parse_ensure!(0 $stack $bail ($($fuel)*) {($($buf)* $div) $($parse)*} ($($rest)*) $($rest)*)
};
(atom $stack:tt $bail:tt (~$($fuel:tt)*) {($($buf:tt)*) $($parse:tt)*} ($rem:tt $($dup:tt)*) % $($rest:tt)*) => {
$crate::__parse_ensure!(0 $stack $bail ($($fuel)*) {($($buf)* $rem) $($parse)*} ($($rest)*) $($rest)*)
};
(atom $stack:tt $bail:tt (~$($fuel:tt)*) {($($buf:tt)*) $($parse:tt)*} ($bitxor:tt $($dup:tt)*) ^ $($rest:tt)*) => {
$crate::__parse_ensure!(0 $stack $bail ($($fuel)*) {($($buf)* $bitxor) $($parse)*} ($($rest)*) $($rest)*)
};
(atom $stack:tt $bail:tt (~$($fuel:tt)*) {($($buf:tt)*) $($parse:tt)*} ($bitand:tt $($dup:tt)*) & $($rest:tt)*) => {
$crate::__parse_ensure!(0 $stack $bail ($($fuel)*) {($($buf)* $bitand) $($parse)*} ($($rest)*) $($rest)*)
};
(atom $stack:tt $bail:tt (~$($fuel:tt)*) {($($buf:tt)*) $($parse:tt)*} ($bitor:tt $($dup:tt)*) | $($rest:tt)*) => {
$crate::__parse_ensure!(0 $stack $bail ($($fuel)*) {($($buf)* $bitor) $($parse)*} ($($rest)*) $($rest)*)
};
(atom $stack:tt $bail:tt (~$($fuel:tt)*) {($($buf:tt)*) $($parse:tt)*} ($shl:tt $($dup:tt)*) << $($rest:tt)*) => {
$crate::__parse_ensure!(0 $stack $bail ($($fuel)*) {($($buf)* $shl) $($parse)*} ($($rest)*) $($rest)*)
};
(atom $stack:tt $bail:tt (~$($fuel:tt)*) {($($buf:tt)*) $($parse:tt)*} ($shr:tt $($dup:tt)*) >> $($rest:tt)*) => {
$crate::__parse_ensure!(0 $stack $bail ($($fuel)*) {($($buf)* $shr) $($parse)*} ($($rest)*) $($rest)*)
};
// low precedence binary operators
(atom ($($stack:tt)+) $bail:tt (~$($fuel:tt)*) {($($buf:tt)*) $($parse:tt)*} ($and:tt $($dup:tt)*) && $($rest:tt)*) => {
$crate::__parse_ensure!(0 ($($stack)*) $bail ($($fuel)*) {($($buf)* $and) $($parse)*} ($($rest)*) $($rest)*)
};
(atom ($($stack:tt)+) $bail:tt (~$($fuel:tt)*) {($($buf:tt)*) $($parse:tt)*} ($or:tt $($dup:tt)*) || $($rest:tt)*) => {
$crate::__parse_ensure!(0 ($($stack)*) $bail ($($fuel)*) {($($buf)* $or) $($parse)*} ($($rest)*) $($rest)*)
};
(atom ($($stack:tt)+) $bail:tt (~$($fuel:tt)*) {($($buf:tt)*) $($parse:tt)*} ($assign:tt $($dup:tt)*) = $($rest:tt)*) => {
$crate::__parse_ensure!(0 ($($stack)*) $bail ($($fuel)*) {($($buf)* $assign) $($parse)*} ($($rest)*) $($rest)*)
};
(atom ($($stack:tt)+) $bail:tt (~$($fuel:tt)*) {($($buf:tt)*) $($parse:tt)*} ($addeq:tt $($dup:tt)*) += $($rest:tt)*) => {
$crate::__parse_ensure!(0 ($($stack)*) $bail ($($fuel)*) {($($buf)* $addeq) $($parse)*} ($($rest)*) $($rest)*)
};
(atom ($($stack:tt)+) $bail:tt (~$($fuel:tt)*) {($($buf:tt)*) $($parse:tt)*} ($subeq:tt $($dup:tt)*) -= $($rest:tt)*) => {
$crate::__parse_ensure!(0 ($($stack)*) $bail ($($fuel)*) {($($buf)* $subeq) $($parse)*} ($($rest)*) $($rest)*)
};
(atom ($($stack:tt)+) $bail:tt (~$($fuel:tt)*) {($($buf:tt)*) $($parse:tt)*} ($muleq:tt $($dup:tt)*) *= $($rest:tt)*) => {
$crate::__parse_ensure!(0 ($($stack)*) $bail ($($fuel)*) {($($buf)* $muleq) $($parse)*} ($($rest)*) $($rest)*)
};
(atom ($($stack:tt)+) $bail:tt (~$($fuel:tt)*) {($($buf:tt)*) $($parse:tt)*} ($diveq:tt $($dup:tt)*) /= $($rest:tt)*) => {
$crate::__parse_ensure!(0 ($($stack)*) $bail ($($fuel)*) {($($buf)* $diveq) $($parse)*} ($($rest)*) $($rest)*)
};
(atom ($($stack:tt)+) $bail:tt (~$($fuel:tt)*) {($($buf:tt)*) $($parse:tt)*} ($remeq:tt $($dup:tt)*) %= $($rest:tt)*) => {
$crate::__parse_ensure!(0 ($($stack)*) $bail ($($fuel)*) {($($buf)* $remeq) $($parse)*} ($($rest)*) $($rest)*)
};
(atom ($($stack:tt)+) $bail:tt (~$($fuel:tt)*) {($($buf:tt)*) $($parse:tt)*} ($bitxoreq:tt $($dup:tt)*) ^= $($rest:tt)*) => {
$crate::__parse_ensure!(0 ($($stack)*) $bail ($($fuel)*) {($($buf)* $bitxoreq) $($parse)*} ($($rest)*) $($rest)*)
};
(atom ($($stack:tt)+) $bail:tt (~$($fuel:tt)*) {($($buf:tt)*) $($parse:tt)*} ($bitandeq:tt $($dup:tt)*) &= $($rest:tt)*) => {
$crate::__parse_ensure!(0 ($($stack)*) $bail ($($fuel)*) {($($buf)* $bitandeq) $($parse)*} ($($rest)*) $($rest)*)
};
(atom ($($stack:tt)+) $bail:tt (~$($fuel:tt)*) {($($buf:tt)*) $($parse:tt)*} ($bitoreq:tt $($dup:tt)*) |= $($rest:tt)*) => {
$crate::__parse_ensure!(0 ($($stack)*) $bail ($($fuel)*) {($($buf)* $bitoreq) $($parse)*} ($($rest)*) $($rest)*)
};
(atom ($($stack:tt)+) $bail:tt (~$($fuel:tt)*) {($($buf:tt)*) $($parse:tt)*} ($shleq:tt $($dup:tt)*) <<= $($rest:tt)*) => {
$crate::__parse_ensure!(0 ($($stack)*) $bail ($($fuel)*) {($($buf)* $shleq) $($parse)*} ($($rest)*) $($rest)*)
};
(atom ($($stack:tt)+) $bail:tt (~$($fuel:tt)*) {($($buf:tt)*) $($parse:tt)*} ($shreq:tt $($dup:tt)*) >>= $($rest:tt)*) => {
$crate::__parse_ensure!(0 ($($stack)*) $bail ($($fuel)*) {($($buf)* $shreq) $($parse)*} ($($rest)*) $($rest)*)
};
// unrecognized expression
($state:tt $stack:tt ($($bail:tt)*) $($rest:tt)*) => {
$crate::__fallback_ensure!($($bail)*)
};
}
#[doc(hidden)]
#[macro_export]
macro_rules! __fancy_ensure {
($lhs:expr, $op:tt, $rhs:expr) => {
match (&$lhs, &$rhs) {
(lhs, rhs) => {
if !(lhs $op rhs) {
#[allow(unused_imports)]
use $crate::__private::{BothDebug, NotBothDebug};
return Err((lhs, rhs).__dispatch_ensure(
$crate::__private::concat!(
"Condition failed: `",
$crate::__private::stringify!($lhs),
" ",
$crate::__private::stringify!($op),
" ",
$crate::__private::stringify!($rhs),
"`",
),
));
}
}
}
};
}
#[doc(hidden)]
#[macro_export]
macro_rules! __fallback_ensure {
($cond:expr $(,)?) => {
if $crate::__private::not($cond) {
return $crate::__private::Err($crate::Error::msg(
$crate::__private::concat!("Condition failed: `", $crate::__private::stringify!($cond), "`")
));
}
};
($cond:expr, $msg:literal $(,)?) => {
if $crate::__private::not($cond) {
return $crate::__private::Err($crate::__anyhow!($msg));
}
};
($cond:expr, $err:expr $(,)?) => {
if $crate::__private::not($cond) {
return $crate::__private::Err($crate::__anyhow!($err));
}
};
($cond:expr, $fmt:expr, $($arg:tt)*) => {
if $crate::__private::not($cond) {
return $crate::__private::Err($crate::__anyhow!($fmt, $($arg)*));
}
};
}
+1059
View File
File diff suppressed because it is too large. Load diff
+158
View File
@@ -0,0 +1,158 @@
use crate::chain::Chain;
use crate::error::ErrorImpl;
use crate::ptr::Ref;
use core::fmt::{self, Debug, Write};
impl ErrorImpl {
pub(crate) unsafe fn display(this: Ref<Self>, f: &mut fmt::Formatter) -> fmt::Result {
write!(f, "{}", unsafe { Self::error(this) })?;
if f.alternate() {
let chain = unsafe { Self::chain(this) };
for cause in chain.skip(1) {
write!(f, ": {}", cause)?;
}
}
Ok(())
}
pub(crate) unsafe fn debug(this: Ref<Self>, f: &mut fmt::Formatter) -> fmt::Result {
let error = unsafe { Self::error(this) };
if f.alternate() {
return Debug::fmt(error, f);
}
write!(f, "{}", error)?;
if let Some(cause) = error.source() {
write!(f, "\n\nCaused by:")?;
let multiple = cause.source().is_some();
for (n, error) in Chain::new(cause).enumerate() {
writeln!(f)?;
let mut indented = Indented {
inner: f,
number: if multiple { Some(n) } else { None },
started: false,
};
write!(indented, "{}", error)?;
}
}
#[cfg(feature = "std")]
{
use alloc::string::ToString;
use std::backtrace::BacktraceStatus;
let backtrace = unsafe { Self::backtrace(this) };
if let BacktraceStatus::Captured = backtrace.status() {
let mut backtrace = backtrace.to_string();
write!(f, "\n\n")?;
if backtrace.starts_with("stack backtrace:") {
// Capitalize to match "Caused by:"
backtrace.replace_range(0..1, "S");
} else {
// "stack backtrace:" prefix was removed in
// https://github.com/rust-lang/backtrace-rs/pull/286
writeln!(f, "Stack backtrace:")?;
}
backtrace.truncate(backtrace.trim_end().len());
write!(f, "{}", backtrace)?;
}
}
Ok(())
}
}
struct Indented<'a, D> {
inner: &'a mut D,
number: Option<usize>,
started: bool,
}
impl<T> Write for Indented<'_, T>
where
T: Write,
{
fn write_str(&mut self, s: &str) -> fmt::Result {
for (i, line) in s.split('\n').enumerate() {
if !self.started {
self.started = true;
match self.number {
Some(number) => write!(self.inner, "{: >5}: ", number)?,
None => self.inner.write_str(" ")?,
}
} else if i > 0 {
self.inner.write_char('\n')?;
if self.number.is_some() {
self.inner.write_str(" ")?;
} else {
self.inner.write_str(" ")?;
}
}
self.inner.write_str(line)?;
}
Ok(())
}
}
#[cfg(test)]
mod tests {
use super::*;
use alloc::string::String;
#[test]
fn one_digit() {
let input = "verify\nthis";
let expected = " 2: verify\n this";
let mut output = String::new();
Indented {
inner: &mut output,
number: Some(2),
started: false,
}
.write_str(input)
.unwrap();
assert_eq!(expected, output);
}
#[test]
fn two_digits() {
let input = "verify\nthis";
let expected = " 12: verify\n this";
let mut output = String::new();
Indented {
inner: &mut output,
number: Some(12),
started: false,
}
.write_str(input)
.unwrap();
assert_eq!(expected, output);
}
#[test]
fn no_digits() {
let input = "verify\nthis";
let expected = " verify\n this";
let mut output = String::new();
Indented {
inner: &mut output,
number: None,
started: false,
}
.write_str(input)
.unwrap();
assert_eq!(expected, output);
}
}
+121
View File
@@ -0,0 +1,121 @@
// Tagged dispatch mechanism for resolving the behavior of `anyhow!($expr)`.
//
// When anyhow! is given a single expr argument to turn into anyhow::Error, we
// want the resulting Error to pick up the input's implementation of source()
// and backtrace() if it has a std::error::Error impl, otherwise require nothing
// more than Display and Debug.
//
// Expressed in terms of specialization, we want something like:
//
// trait AnyhowNew {
// fn new(self) -> Error;
// }
//
// impl<T> AnyhowNew for T
// where
// T: Display + Debug + Send + Sync + 'static,
// {
// default fn new(self) -> Error {
// /* no std error impl */
// }
// }
//
// impl<T> AnyhowNew for T
// where
// T: std::error::Error + Send + Sync + 'static,
// {
// fn new(self) -> Error {
// /* use std error's source() and backtrace() */
// }
// }
//
// Since specialization is not stable yet, instead we rely on autoref behavior
// of method resolution to perform tagged dispatch. Here we have two traits
// AdhocKind and TraitKind that both have an anyhow_kind() method. AdhocKind is
// implemented whether or not the caller's type has a std error impl, while
// TraitKind is implemented only when a std error impl does exist. The ambiguity
// is resolved by AdhocKind requiring an extra autoref so that it has lower
// precedence.
//
// The anyhow! macro will set up the call in this form:
//
// #[allow(unused_imports)]
// use $crate::__private::{AdhocKind, TraitKind};
// let error = $msg;
// (&error).anyhow_kind().new(error)
use crate::Error;
use core::fmt::{Debug, Display};
#[cfg(any(feature = "std", not(anyhow_no_core_error)))]
use crate::StdError;
#[cfg(any(feature = "std", not(anyhow_no_core_error)))]
use alloc::boxed::Box;
pub struct Adhoc;
#[doc(hidden)]
pub trait AdhocKind: Sized {
#[inline]
fn anyhow_kind(&self) -> Adhoc {
Adhoc
}
}
impl<T> AdhocKind for &T where T: ?Sized + Display + Debug + Send + Sync + 'static {}
impl Adhoc {
#[cold]
pub fn new<M>(self, message: M) -> Error
where
M: Display + Debug + Send + Sync + 'static,
{
Error::construct_from_adhoc(message, backtrace!())
}
}
pub struct Trait;
#[doc(hidden)]
pub trait TraitKind: Sized {
#[inline]
fn anyhow_kind(&self) -> Trait {
Trait
}
}
impl<E> TraitKind for E where E: Into<Error> {}
impl Trait {
#[cold]
pub fn new<E>(self, error: E) -> Error
where
E: Into<Error>,
{
error.into()
}
}
#[cfg(any(feature = "std", not(anyhow_no_core_error)))]
pub struct Boxed;
#[cfg(any(feature = "std", not(anyhow_no_core_error)))]
#[doc(hidden)]
pub trait BoxedKind: Sized {
#[inline]
fn anyhow_kind(&self) -> Boxed {
Boxed
}
}
#[cfg(any(feature = "std", not(anyhow_no_core_error)))]
impl BoxedKind for Box<dyn StdError + Send + Sync> {}
#[cfg(any(feature = "std", not(anyhow_no_core_error)))]
impl Boxed {
#[cold]
pub fn new(self, error: Box<dyn StdError + Send + Sync>) -> Error {
let backtrace = backtrace_if_absent!(&*error);
Error::construct_from_boxed(error, backtrace)
}
}
+728
View File
@@ -0,0 +1,728 @@
//! [![github]](https://github.com/dtolnay/anyhow)&ensp;[![crates-io]](https://crates.io/crates/anyhow)&ensp;[![docs-rs]](https://docs.rs/anyhow)
//!
//! [github]: https://img.shields.io/badge/github-8da0cb?style=for-the-badge&labelColor=555555&logo=github
//! [crates-io]: https://img.shields.io/badge/crates.io-fc8d62?style=for-the-badge&labelColor=555555&logo=rust
//! [docs-rs]: https://img.shields.io/badge/docs.rs-66c2a5?style=for-the-badge&labelColor=555555&logo=docs.rs
//!
//! <br>
//!
//! This library provides [`anyhow::Error`][Error], a trait object based error
//! type for easy idiomatic error handling in Rust applications.
//!
//! <br>
//!
//! # Details
//!
//! - Use `Result<T, anyhow::Error>`, or equivalently `anyhow::Result<T>`, as
//! the return type of any fallible function.
//!
//! Within the function, use `?` to easily propagate any error that implements
//! the [`std::error::Error`] trait.
//!
//! ```
//! # pub trait Deserialize {}
//! #
//! # mod serde_json {
//! # use super::Deserialize;
//! # use std::io;
//! #
//! # pub fn from_str<T: Deserialize>(json: &str) -> io::Result<T> {
//! # unimplemented!()
//! # }
//! # }
//! #
//! # struct ClusterMap;
//! #
//! # impl Deserialize for ClusterMap {}
//! #
//! use anyhow::Result;
//!
//! fn get_cluster_info() -> Result<ClusterMap> {
//! let config = std::fs::read_to_string("cluster.json")?;
//! let map: ClusterMap = serde_json::from_str(&config)?;
//! Ok(map)
//! }
//! #
//! # fn main() {}
//! ```
//!
//! - Attach context to help the person troubleshooting the error understand
//! where things went wrong. A low-level error like "No such file or
//! directory" can be annoying to debug without more context about what higher
//! level step the application was in the middle of.
//!
//! ```
//! # struct It;
//! #
//! # impl It {
//! # fn detach(&self) -> Result<()> {
//! # unimplemented!()
//! # }
//! # }
//! #
//! use anyhow::{Context, Result};
//!
//! fn main() -> Result<()> {
//! # return Ok(());
//! #
//! # const _: &str = stringify! {
//! ...
//! # };
//! #
//! # let it = It;
//! # let path = "./path/to/instrs.json";
//! #
//! it.detach().context("Failed to detach the important thing")?;
//!
//! let content = std::fs::read(path)
//! .with_context(|| format!("Failed to read instrs from {}", path))?;
//! #
//! # const _: &str = stringify! {
//! ...
//! # };
//! #
//! # Ok(())
//! }
//! ```
//!
//! ```console
//! Error: Failed to read instrs from ./path/to/instrs.json
//!
//! Caused by:
//! No such file or directory (os error 2)
//! ```
//!
//! - Downcasting is supported and can be by value, by shared reference, or by
//! mutable reference as needed.
//!
//! ```
//! # use anyhow::anyhow;
//! # use std::fmt::{self, Display};
//! # use std::task::Poll;
//! #
//! # #[derive(Debug)]
//! # enum DataStoreError {
//! # Censored(()),
//! # }
//! #
//! # impl Display for DataStoreError {
//! # fn fmt(&self, formatter: &mut fmt::Formatter) -> fmt::Result {
//! # unimplemented!()
//! # }
//! # }
//! #
//! # impl std::error::Error for DataStoreError {}
//! #
//! # const REDACTED_CONTENT: () = ();
//! #
//! # let error = anyhow!("...");
//! # let root_cause = &error;
//! #
//! # let ret =
//! // If the error was caused by redaction, then return a
//! // tombstone instead of the content.
//! match root_cause.downcast_ref::<DataStoreError>() {
//! Some(DataStoreError::Censored(_)) => Ok(Poll::Ready(REDACTED_CONTENT)),
//! None => Err(error),
//! }
//! # ;
//! ```
//!
//! - If using Rust &ge; 1.65, a backtrace is captured and printed with the
//! error if the underlying error type does not already provide its own. In
//! order to see backtraces, they must be enabled through the environment
//! variables described in [`std::backtrace`]:
//!
//! - If you want panics and errors to both have backtraces, set
//! `RUST_BACKTRACE=1`;
//! - If you want only errors to have backtraces, set `RUST_LIB_BACKTRACE=1`;
//! - If you want only panics to have backtraces, set `RUST_BACKTRACE=1` and
//! `RUST_LIB_BACKTRACE=0`.
//!
//! [`std::backtrace`]: std::backtrace#environment-variables
//!
//! - Anyhow works with any error type that has an impl of `std::error::Error`,
//! including ones defined in your crate. We do not bundle a `derive(Error)`
//! macro but you can write the impls yourself or use a standalone macro like
//! [thiserror].
//!
//! [thiserror]: https://github.com/dtolnay/thiserror
//!
//! ```
//! use thiserror::Error;
//!
//! #[derive(Error, Debug)]
//! pub enum FormatError {
//! #[error("Invalid header (expected {expected:?}, got {found:?})")]
//! InvalidHeader {
//! expected: String,
//! found: String,
//! },
//! #[error("Missing attribute: {0}")]
//! MissingAttribute(String),
//! }
//! ```
//!
//! - One-off error messages can be constructed using the `anyhow!` macro, which
//! supports string interpolation and produces an `anyhow::Error`.
//!
//! ```
//! # use anyhow::{anyhow, Result};
//! #
//! # fn demo() -> Result<()> {
//! # let missing = "...";
//! return Err(anyhow!("Missing attribute: {}", missing));
//! # Ok(())
//! # }
//! ```
//!
//! A `bail!` macro is provided as a shorthand for the same early return.
//!
//! ```
//! # use anyhow::{bail, Result};
//! #
//! # fn demo() -> Result<()> {
//! # let missing = "...";
//! bail!("Missing attribute: {}", missing);
//! # Ok(())
//! # }
//! ```
//!
//! <br>
//!
//! # No-std support
//!
//! In no_std mode, almost all of the same API is available and works the same
//! way. To depend on Anyhow in no_std mode, disable our default enabled "std"
//! feature in Cargo.toml. A global allocator is required.
//!
//! ```toml
//! [dependencies]
//! anyhow = { version = "1.0", default-features = false }
//! ```
//!
//! With versions of Rust older than 1.81, no_std mode may require an additional
//! `.map_err(Error::msg)` when working with a non-Anyhow error type inside a
//! function that returns Anyhow's error type, as the trait that `?`-based error
//! conversions are defined by is only available in std in those old versions.
#![doc(html_root_url = "https://docs.rs/anyhow/1.0.102")]
#![cfg_attr(error_generic_member_access, feature(error_generic_member_access))]
#![no_std]
#![deny(dead_code, unsafe_op_in_unsafe_fn, unused_imports, unused_mut)]
#![allow(
clippy::doc_markdown,
clippy::elidable_lifetime_names,
clippy::enum_glob_use,
clippy::explicit_auto_deref,
clippy::extra_unused_type_parameters,
clippy::incompatible_msrv,
clippy::let_underscore_untyped,
clippy::missing_errors_doc,
clippy::missing_panics_doc,
clippy::module_name_repetitions,
clippy::must_use_candidate,
clippy::needless_doctest_main,
clippy::needless_lifetimes,
clippy::new_ret_no_self,
clippy::redundant_else,
clippy::return_self_not_must_use,
clippy::struct_field_names,
clippy::uninlined_format_args,
clippy::unused_self,
clippy::used_underscore_binding,
clippy::wildcard_imports,
clippy::wrong_self_convention
)]
#![allow(unknown_lints, mismatched_lifetime_syntaxes)]
#[cfg(all(
anyhow_nightly_testing,
feature = "std",
not(error_generic_member_access)
))]
compile_error!("Build script probe failed to compile.");
extern crate alloc;
#[cfg(feature = "std")]
extern crate std;
#[macro_use]
mod backtrace;
mod chain;
mod context;
mod ensure;
mod error;
mod fmt;
mod kind;
mod macros;
#[cfg(error_generic_member_access)]
mod nightly;
mod ptr;
mod wrapper;
use crate::error::ErrorImpl;
use crate::ptr::Own;
use core::fmt::Display;
#[cfg(all(not(feature = "std"), anyhow_no_core_error))]
use core::fmt::Debug;
#[cfg(feature = "std")]
use std::error::Error as StdError;
#[cfg(not(any(feature = "std", anyhow_no_core_error)))]
use core::error::Error as StdError;
#[cfg(all(not(feature = "std"), anyhow_no_core_error))]
trait StdError: Debug + Display {
fn source(&self) -> Option<&(dyn StdError + 'static)> {
None
}
}
#[doc(no_inline)]
pub use anyhow as format_err;
/// The `Error` type, a wrapper around a dynamic error type.
///
/// `Error` works a lot like `Box<dyn std::error::Error>`, but with these
/// differences:
///
/// - `Error` requires that the error is `Send`, `Sync`, and `'static`.
/// - `Error` guarantees that a backtrace is available, even if the underlying
/// error type does not provide one.
/// - `Error` is represented as a narrow pointer &mdash; exactly one word in
/// size instead of two.
///
/// <br>
///
/// # Display representations
///
/// When you print an error object using "{}" or to_string(), only the outermost
/// underlying error or context is printed, not any of the lower level causes.
/// This is exactly as if you had called the Display impl of the error from
/// which you constructed your anyhow::Error.
///
/// ```console
/// Failed to read instrs from ./path/to/instrs.json
/// ```
///
/// To print causes as well using anyhow's default formatting of causes, use the
/// alternate selector "{:#}".
///
/// ```console
/// Failed to read instrs from ./path/to/instrs.json: No such file or directory (os error 2)
/// ```
///
/// The Debug format "{:?}" includes your backtrace if one was captured. Note
/// that this is the representation you get by default if you return an error
/// from `fn main` instead of printing it explicitly yourself.
///
/// ```console
/// Error: Failed to read instrs from ./path/to/instrs.json
///
/// Caused by:
/// No such file or directory (os error 2)
/// ```
///
/// and if there is a backtrace available:
///
/// ```console
/// Error: Failed to read instrs from ./path/to/instrs.json
///
/// Caused by:
/// No such file or directory (os error 2)
///
/// Stack backtrace:
/// 0: <E as anyhow::context::ext::StdError>::ext_context
/// at /git/anyhow/src/backtrace.rs:26
/// 1: core::result::Result<T,E>::map_err
/// at /git/rustc/src/libcore/result.rs:596
/// 2: anyhow::context::<impl anyhow::Context<T,E> for core::result::Result<T,E>>::with_context
/// at /git/anyhow/src/context.rs:58
/// 3: testing::main
/// at src/main.rs:5
/// 4: std::rt::lang_start
/// at /git/rustc/src/libstd/rt.rs:61
/// 5: main
/// 6: __libc_start_main
/// 7: _start
/// ```
///
/// To see a conventional struct-style Debug representation, use "{:#?}".
///
/// ```console
/// Error {
/// context: "Failed to read instrs from ./path/to/instrs.json",
/// source: Os {
/// code: 2,
/// kind: NotFound,
/// message: "No such file or directory",
/// },
/// }
/// ```
///
/// If none of the built-in representations are appropriate and you would prefer
/// to render the error and its cause chain yourself, it can be done something
/// like this:
///
/// ```
/// use anyhow::{Context, Result};
///
/// fn main() {
/// if let Err(err) = try_main() {
/// eprintln!("ERROR: {}", err);
/// err.chain().skip(1).for_each(|cause| eprintln!("because: {}", cause));
/// std::process::exit(1);
/// }
/// }
///
/// fn try_main() -> Result<()> {
/// # const IGNORE: &str = stringify! {
/// ...
/// # };
/// # Ok(())
/// }
/// ```
#[repr(transparent)]
pub struct Error {
inner: Own<ErrorImpl>,
}
/// Iterator of a chain of source errors.
///
/// This type is the iterator returned by [`Error::chain`].
///
/// # Example
///
/// ```
/// use anyhow::Error;
/// use std::io;
///
/// pub fn underlying_io_error_kind(error: &Error) -> Option<io::ErrorKind> {
/// for cause in error.chain() {
/// if let Some(io_error) = cause.downcast_ref::<io::Error>() {
/// return Some(io_error.kind());
/// }
/// }
/// None
/// }
/// ```
#[cfg(any(feature = "std", not(anyhow_no_core_error)))]
#[derive(Clone)]
pub struct Chain<'a> {
state: crate::chain::ChainState<'a>,
}
/// `Result<T, Error>`
///
/// This is a reasonable return type to use throughout your application but also
/// for `fn main`; if you do, failures will be printed along with any
/// [context][Context] and a backtrace if one was captured.
///
/// `anyhow::Result` may be used with one *or* two type parameters.
///
/// ```rust
/// use anyhow::Result;
///
/// # const IGNORE: &str = stringify! {
/// fn demo1() -> Result<T> {...}
/// // ^ equivalent to std::result::Result<T, anyhow::Error>
///
/// fn demo2() -> Result<T, OtherError> {...}
/// // ^ equivalent to std::result::Result<T, OtherError>
/// # };
/// ```
///
/// # Example
///
/// ```
/// # pub trait Deserialize {}
/// #
/// # mod serde_json {
/// # use super::Deserialize;
/// # use std::io;
/// #
/// # pub fn from_str<T: Deserialize>(json: &str) -> io::Result<T> {
/// # unimplemented!()
/// # }
/// # }
/// #
/// # #[derive(Debug)]
/// # struct ClusterMap;
/// #
/// # impl Deserialize for ClusterMap {}
/// #
/// use anyhow::Result;
///
/// fn main() -> Result<()> {
/// # return Ok(());
/// let config = std::fs::read_to_string("cluster.json")?;
/// let map: ClusterMap = serde_json::from_str(&config)?;
/// println!("cluster info: {:#?}", map);
/// Ok(())
/// }
/// ```
pub type Result<T, E = Error> = core::result::Result<T, E>;
/// Provides the `context` method for `Result`.
///
/// This trait is sealed and cannot be implemented for types outside of
/// `anyhow`.
///
/// <br>
///
/// # Example
///
/// ```
/// use anyhow::{Context, Result};
/// use std::fs;
/// use std::path::PathBuf;
///
/// pub struct ImportantThing {
/// path: PathBuf,
/// }
///
/// impl ImportantThing {
/// # const IGNORE: &'static str = stringify! {
/// pub fn detach(&mut self) -> Result<()> {...}
/// # };
/// # fn detach(&mut self) -> Result<()> {
/// # unimplemented!()
/// # }
/// }
///
/// pub fn do_it(mut it: ImportantThing) -> Result<Vec<u8>> {
/// it.detach().context("Failed to detach the important thing")?;
///
/// let path = &it.path;
/// let content = fs::read(path)
/// .with_context(|| format!("Failed to read instrs from {}", path.display()))?;
///
/// Ok(content)
/// }
/// ```
///
/// When printed, the outermost context would be printed first and the lower
/// level underlying causes would be enumerated below.
///
/// ```console
/// Error: Failed to read instrs from ./path/to/instrs.json
///
/// Caused by:
/// No such file or directory (os error 2)
/// ```
///
/// Refer to the [Display representations] documentation for other forms in
/// which this context chain can be rendered.
///
/// [Display representations]: Error#display-representations
///
/// <br>
///
/// # Effect on downcasting
///
/// After attaching context of type `C` onto an error of type `E`, the resulting
/// `anyhow::Error` may be downcast to `C` **or** to `E`.
///
/// That is, in codebases that rely on downcasting, Anyhow's context supports
/// both of the following use cases:
///
/// - **Attaching context whose type is insignificant onto errors whose type
/// is used in downcasts.**
///
/// In other error libraries whose context is not designed this way, it can
/// be risky to introduce context to existing code because new context might
/// break existing working downcasts. In Anyhow, any downcast that worked
/// before adding context will continue to work after you add a context, so
/// you should freely add human-readable context to errors wherever it would
/// be helpful.
///
/// ```
/// # use anyhow::bail;
/// # use thiserror::Error;
/// #
/// # #[derive(Error, Debug)]
/// # #[error("???")]
/// # struct SuspiciousError;
/// #
/// # fn helper() -> Result<()> {
/// # bail!(SuspiciousError);
/// # }
/// #
/// use anyhow::{Context, Result};
///
/// fn do_it() -> Result<()> {
/// helper().context("Failed to complete the work")?;
/// # const IGNORE: &str = stringify! {
/// ...
/// # };
/// # unreachable!()
/// }
///
/// fn main() {
/// let err = do_it().unwrap_err();
/// if let Some(e) = err.downcast_ref::<SuspiciousError>() {
/// // If helper() returned SuspiciousError, this downcast will
/// // correctly succeed even with the context in between.
/// # return;
/// }
/// # panic!("expected downcast to succeed");
/// }
/// ```
///
/// - **Attaching context whose type is used in downcasts onto errors whose
/// type is insignificant.**
///
/// Some codebases prefer to use machine-readable context to categorize
/// lower level errors in a way that will be actionable to higher levels of
/// the application.
///
/// ```
/// # use anyhow::bail;
/// # use thiserror::Error;
/// #
/// # #[derive(Error, Debug)]
/// # #[error("???")]
/// # struct HelperFailed;
/// #
/// # fn helper() -> Result<()> {
/// # bail!("no such file or directory");
/// # }
/// #
/// use anyhow::{Context, Result};
///
/// fn do_it() -> Result<()> {
/// helper().context(HelperFailed)?;
/// # const IGNORE: &str = stringify! {
/// ...
/// # };
/// # unreachable!()
/// }
///
/// fn main() {
/// let err = do_it().unwrap_err();
/// if let Some(e) = err.downcast_ref::<HelperFailed>() {
/// // If helper failed, this downcast will succeed because
/// // HelperFailed is the context that has been attached to
/// // that error.
/// # return;
/// }
/// # panic!("expected downcast to succeed");
/// }
/// ```
pub trait Context<T, E>: context::private::Sealed {
/// Wrap the error value with additional context.
fn context<C>(self, context: C) -> Result<T, Error>
where
C: Display + Send + Sync + 'static;
/// Wrap the error value with additional context that is evaluated lazily
/// only once an error does occur.
fn with_context<C, F>(self, f: F) -> Result<T, Error>
where
C: Display + Send + Sync + 'static,
F: FnOnce() -> C;
}
/// Equivalent to `Ok::<_, anyhow::Error>(value)`.
///
/// This simplifies creation of an `anyhow::Result` in places where type
/// inference cannot deduce the `E` type of the result &mdash; without needing
/// to write `Ok::<_, anyhow::Error>(value)`.
///
/// One might think that `anyhow::Result::Ok(value)` would work in such cases
/// but it does not.
///
/// ```console
/// error[E0282]: type annotations needed for `std::result::Result<i32, E>`
/// --> src/main.rs:11:13
/// |
/// 11 | let _ = anyhow::Result::Ok(1);
/// | - ^^^^^^^^^^^^^^^^^^ cannot infer type for type parameter `E` declared on the enum `Result`
/// | |
/// | consider giving this pattern the explicit type `std::result::Result<i32, E>`, where the type parameter `E` is specified
/// ```
#[allow(non_snake_case)]
#[inline]
pub fn Ok<T>(value: T) -> Result<T> {
Result::Ok(value)
}
// Not public API. Referenced by macro-generated code.
#[doc(hidden)]
pub mod __private {
use self::not::Bool;
use crate::Error;
use alloc::fmt;
use core::fmt::Arguments;
#[doc(hidden)]
pub use crate::ensure::{BothDebug, NotBothDebug};
#[doc(hidden)]
pub use alloc::format;
#[doc(hidden)]
pub use core::result::Result::Err;
#[doc(hidden)]
pub use core::{concat, format_args, stringify};
#[doc(hidden)]
pub mod kind {
#[doc(hidden)]
pub use crate::kind::{AdhocKind, TraitKind};
#[cfg(any(feature = "std", not(anyhow_no_core_error)))]
#[doc(hidden)]
pub use crate::kind::BoxedKind;
}
#[doc(hidden)]
#[inline]
#[cold]
pub fn format_err(args: Arguments) -> Error {
if let Some(message) = args.as_str() {
// anyhow!("literal"), can downcast to &'static str
Error::msg(message)
} else {
// anyhow!("interpolate {var}"), can downcast to String
Error::msg(fmt::format(args))
}
}
#[doc(hidden)]
#[inline]
#[cold]
#[must_use]
pub fn must_use(error: Error) -> Error {
error
}
#[doc(hidden)]
#[inline]
pub fn not(cond: impl Bool) -> bool {
cond.not()
}
mod not {
#[doc(hidden)]
pub trait Bool {
fn not(self) -> bool;
}
impl Bool for bool {
#[inline]
fn not(self) -> bool {
!self
}
}
impl Bool for &bool {
#[inline]
fn not(self) -> bool {
!*self
}
}
}
}
+245
View File
@@ -0,0 +1,245 @@
/// Return early with an error.
///
/// This macro is equivalent to
/// <code>return Err([anyhow!($args\...)][anyhow!])</code>.
///
/// The surrounding function's or closure's return value is required to be
/// <code>Result&lt;_, [anyhow::Error][crate::Error]&gt;</code>.
///
/// [anyhow!]: crate::anyhow
///
/// # Example
///
/// ```
/// # use anyhow::{bail, Result};
/// #
/// # fn has_permission(user: usize, resource: usize) -> bool {
/// # true
/// # }
/// #
/// # fn main() -> Result<()> {
/// # let user = 0;
/// # let resource = 0;
/// #
/// if !has_permission(user, resource) {
/// bail!("permission denied for accessing {}", resource);
/// }
/// # Ok(())
/// # }
/// ```
///
/// ```
/// # use anyhow::{bail, Result};
/// # use thiserror::Error;
/// #
/// # const MAX_DEPTH: usize = 1;
/// #
/// #[derive(Error, Debug)]
/// enum ScienceError {
/// #[error("recursion limit exceeded")]
/// RecursionLimitExceeded,
/// # #[error("...")]
/// # More = (stringify! {
/// ...
/// # }, 1).1,
/// }
///
/// # fn main() -> Result<()> {
/// # let depth = 0;
/// #
/// if depth > MAX_DEPTH {
/// bail!(ScienceError::RecursionLimitExceeded);
/// }
/// # Ok(())
/// # }
/// ```
#[macro_export]
#[cfg_attr(not(anyhow_no_clippy_format_args), clippy::format_args)]
macro_rules! bail {
($msg:literal $(,)?) => {
return $crate::__private::Err($crate::__anyhow!($msg))
};
($err:expr $(,)?) => {
return $crate::__private::Err($crate::__anyhow!($err))
};
($fmt:expr, $($arg:tt)*) => {
return $crate::__private::Err($crate::__anyhow!($fmt, $($arg)*))
};
}
macro_rules! __ensure {
($ensure:item) => {
/// Return early with an error if a condition is not satisfied.
///
/// This macro is equivalent to
/// <code>if !$cond { return Err([anyhow!($args\...)][anyhow!]); }</code>.
///
/// The surrounding function's or closure's return value is required to be
/// <code>Result&lt;_, [anyhow::Error][crate::Error]&gt;</code>.
///
/// Analogously to `assert!`, `ensure!` takes a condition and exits the function
/// if the condition fails. Unlike `assert!`, `ensure!` returns an `Error`
/// rather than panicking.
///
/// [anyhow!]: crate::anyhow
///
/// # Example
///
/// ```
/// # use anyhow::{ensure, Result};
/// #
/// # fn main() -> Result<()> {
/// # let user = 0;
/// #
/// ensure!(user == 0, "only user 0 is allowed");
/// # Ok(())
/// # }
/// ```
///
/// ```
/// # use anyhow::{ensure, Result};
/// # use thiserror::Error;
/// #
/// # const MAX_DEPTH: usize = 1;
/// #
/// #[derive(Error, Debug)]
/// enum ScienceError {
/// #[error("recursion limit exceeded")]
/// RecursionLimitExceeded,
/// # #[error("...")]
/// # More = (stringify! {
/// ...
/// # }, 1).1,
/// }
///
/// # fn main() -> Result<()> {
/// # let depth = 0;
/// #
/// ensure!(depth <= MAX_DEPTH, ScienceError::RecursionLimitExceeded);
/// # Ok(())
/// # }
/// ```
$ensure
};
}
#[cfg(doc)]
__ensure![
#[macro_export]
macro_rules! ensure {
($cond:expr $(,)?) => {
if !$cond {
return $crate::__private::Err($crate::Error::msg(
$crate::__private::concat!("Condition failed: `", $crate::__private::stringify!($cond), "`")
));
}
};
($cond:expr, $msg:literal $(,)?) => {
if !$cond {
return $crate::__private::Err($crate::__anyhow!($msg));
}
};
($cond:expr, $err:expr $(,)?) => {
if !$cond {
return $crate::__private::Err($crate::__anyhow!($err));
}
};
($cond:expr, $fmt:expr, $($arg:tt)*) => {
if !$cond {
return $crate::__private::Err($crate::__anyhow!($fmt, $($arg)*));
}
};
}
];
#[cfg(not(doc))]
__ensure![
#[macro_export]
#[cfg_attr(not(anyhow_no_clippy_format_args), clippy::format_args)]
macro_rules! ensure {
($($tt:tt)*) => {
$crate::__parse_ensure!(
/* state */ 0
/* stack */ ()
/* bail */ ($($tt)*)
/* fuel */ (~~~~~~~~~~ ~~~~~~~~~~ ~~~~~~~~~~ ~~~~~~~~~~ ~~~~~~~~~~ ~~~~~~~~~~ ~~~~~~~~~~ ~~~~~~~~~~ ~~~~~~~~~~ ~~~~~~~~~~ ~~~~~~~~~~ ~~~~~~~~~~)
/* parse */ {()}
/* dup */ ($($tt)*)
/* rest */ $($tt)*
)
};
}
];
/// Construct an ad-hoc error from a string or existing non-`anyhow` error
/// value.
///
/// This evaluates to an [`Error`][crate::Error]. It can take either just a
/// string, or a format string with arguments. It also can take any custom type
/// which implements `Debug` and `Display`.
///
/// If called with a single argument whose type implements `std::error::Error`
/// (in addition to `Debug` and `Display`, which are always required), then that
/// Error impl's `source` is preserved as the `source` of the resulting
/// `anyhow::Error`.
///
/// # Example
///
/// ```
/// # type V = ();
/// #
/// use anyhow::{anyhow, Result};
///
/// fn lookup(key: &str) -> Result<V> {
/// if key.len() != 16 {
/// return Err(anyhow!("key length must be 16 characters, got {:?}", key));
/// }
///
/// // ...
/// # Ok(())
/// }
/// ```
#[macro_export]
#[cfg_attr(not(anyhow_no_clippy_format_args), clippy::format_args)]
macro_rules! anyhow {
($msg:literal $(,)?) => {
$crate::__private::must_use({
let error = $crate::__private::format_err($crate::__private::format_args!($msg));
error
})
};
($err:expr $(,)?) => {
$crate::__private::must_use({
use $crate::__private::kind::*;
let error = match $err {
error => (&error).anyhow_kind().new(error),
};
error
})
};
($fmt:expr, $($arg:tt)*) => {
$crate::Error::msg($crate::__private::format!($fmt, $($arg)*))
};
}
// Not public API. This is used in the implementation of some of the other
// macros, in which the must_use call is not needed because the value is known
// to be used.
#[doc(hidden)]
#[macro_export]
macro_rules! __anyhow {
($msg:literal $(,)?) => ({
let error = $crate::__private::format_err($crate::__private::format_args!($msg));
error
});
($err:expr $(,)?) => ({
use $crate::__private::kind::*;
let error = match $err {
error => (&error).anyhow_kind().new(error),
};
error
});
($fmt:expr, $($arg:tt)*) => {
$crate::Error::msg($crate::__private::format!($fmt, $($arg)*))
};
}
+58
View File
@@ -0,0 +1,58 @@
// This code exercises the surface area that we expect of the Error generic
// member access API. If the current toolchain is able to compile it, then
// anyhow is able to provide backtrace support.
#![cfg_attr(anyhow_build_probe, feature(error_generic_member_access))]
use core::error::{self, Error};
use std::backtrace::Backtrace;
pub use core::error::Request;
#[cfg(anyhow_build_probe)]
const _: () = {
use core::fmt::{self, Debug, Display};
struct MyError(Backtrace);
impl Debug for MyError {
fn fmt(&self, _formatter: &mut fmt::Formatter) -> fmt::Result {
unimplemented!()
}
}
impl Display for MyError {
fn fmt(&self, _formatter: &mut fmt::Formatter) -> fmt::Result {
unimplemented!()
}
}
impl Error for MyError {
fn provide<'a>(&'a self, request: &mut Request<'a>) {
provide_ref_backtrace(request, &self.0);
}
}
};
// Include in sccache cache key.
#[cfg(anyhow_build_probe)]
const _: Option<&str> = option_env!("RUSTC_BOOTSTRAP");
pub fn request_ref_backtrace(err: &dyn Error) -> Option<&Backtrace> {
request_ref::<Backtrace>(err)
}
fn request_ref<'a, T>(err: &'a (impl Error + ?Sized)) -> Option<&'a T>
where
T: 'static + ?Sized,
{
error::request_ref::<T>(err)
}
pub fn provide_ref_backtrace<'a>(request: &mut Request<'a>, backtrace: &'a Backtrace) {
Request::provide_ref(request, backtrace);
}
pub fn provide<'a>(err: &'a (impl Error + ?Sized), request: &mut Request<'a>) {
Error::provide(err, request);
}
+187
View File
@@ -0,0 +1,187 @@
use alloc::boxed::Box;
use core::marker::PhantomData;
use core::ptr::NonNull;
#[repr(transparent)]
pub struct Own<T>
where
T: ?Sized,
{
pub ptr: NonNull<T>,
}
unsafe impl<T> Send for Own<T> where T: ?Sized {}
unsafe impl<T> Sync for Own<T> where T: ?Sized {}
impl<T> Copy for Own<T> where T: ?Sized {}
impl<T> Clone for Own<T>
where
T: ?Sized,
{
fn clone(&self) -> Self {
*self
}
}
impl<T> Own<T>
where
T: ?Sized,
{
pub fn new(ptr: Box<T>) -> Self {
Own {
ptr: unsafe { NonNull::new_unchecked(Box::into_raw(ptr)) },
}
}
pub fn cast<U: CastTo>(self) -> Own<U::Target> {
Own {
ptr: self.ptr.cast(),
}
}
pub unsafe fn boxed(self) -> Box<T> {
unsafe { Box::from_raw(self.ptr.as_ptr()) }
}
pub fn by_ref(&self) -> Ref<T> {
Ref {
ptr: self.ptr,
lifetime: PhantomData,
}
}
pub fn by_mut(&mut self) -> Mut<T> {
Mut {
ptr: self.ptr,
lifetime: PhantomData,
}
}
}
#[repr(transparent)]
pub struct Ref<'a, T>
where
T: ?Sized,
{
pub ptr: NonNull<T>,
lifetime: PhantomData<&'a T>,
}
impl<'a, T> Copy for Ref<'a, T> where T: ?Sized {}
impl<'a, T> Clone for Ref<'a, T>
where
T: ?Sized,
{
fn clone(&self) -> Self {
*self
}
}
impl<'a, T> Ref<'a, T>
where
T: ?Sized,
{
pub fn new(ptr: &'a T) -> Self {
Ref {
ptr: NonNull::from(ptr),
lifetime: PhantomData,
}
}
pub fn from_raw(ptr: NonNull<T>) -> Self {
Ref {
ptr,
lifetime: PhantomData,
}
}
pub fn cast<U: CastTo>(self) -> Ref<'a, U::Target> {
Ref {
ptr: self.ptr.cast(),
lifetime: PhantomData,
}
}
pub fn by_mut(self) -> Mut<'a, T> {
Mut {
ptr: self.ptr,
lifetime: PhantomData,
}
}
pub fn as_ptr(self) -> *const T {
self.ptr.as_ptr().cast_const()
}
pub unsafe fn deref(self) -> &'a T {
unsafe { &*self.ptr.as_ptr() }
}
}
#[repr(transparent)]
pub struct Mut<'a, T>
where
T: ?Sized,
{
pub ptr: NonNull<T>,
lifetime: PhantomData<&'a mut T>,
}
impl<'a, T> Copy for Mut<'a, T> where T: ?Sized {}
impl<'a, T> Clone for Mut<'a, T>
where
T: ?Sized,
{
fn clone(&self) -> Self {
*self
}
}
impl<'a, T> Mut<'a, T>
where
T: ?Sized,
{
pub fn cast<U: CastTo>(self) -> Mut<'a, U::Target> {
Mut {
ptr: self.ptr.cast(),
lifetime: PhantomData,
}
}
pub fn by_ref(self) -> Ref<'a, T> {
Ref {
ptr: self.ptr,
lifetime: PhantomData,
}
}
pub fn extend<'b>(self) -> Mut<'b, T> {
Mut {
ptr: self.ptr,
lifetime: PhantomData,
}
}
pub unsafe fn deref_mut(self) -> &'a mut T {
unsafe { &mut *self.ptr.as_ptr() }
}
}
impl<'a, T> Mut<'a, T> {
pub unsafe fn read(self) -> T {
unsafe { self.ptr.as_ptr().read() }
}
}
// Force turbofish on all calls of `.cast::<U>()`.
pub trait CastTo {
type Target;
}
impl<T> CastTo for T {
type Target = T;
}
+84
View File
@@ -0,0 +1,84 @@
use crate::StdError;
use core::fmt::{self, Debug, Display};
#[cfg(any(feature = "std", not(anyhow_no_core_error)))]
use alloc::boxed::Box;
#[cfg(error_generic_member_access)]
use crate::nightly::{self, Request};
#[repr(transparent)]
pub struct MessageError<M>(pub M);
impl<M> Debug for MessageError<M>
where
M: Display + Debug,
{
fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
Debug::fmt(&self.0, f)
}
}
impl<M> Display for MessageError<M>
where
M: Display + Debug,
{
fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
Display::fmt(&self.0, f)
}
}
impl<M> StdError for MessageError<M> where M: Display + Debug + 'static {}
#[repr(transparent)]
pub struct DisplayError<M>(pub M);
impl<M> Debug for DisplayError<M>
where
M: Display,
{
fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
Display::fmt(&self.0, f)
}
}
impl<M> Display for DisplayError<M>
where
M: Display,
{
fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
Display::fmt(&self.0, f)
}
}
impl<M> StdError for DisplayError<M> where M: Display + 'static {}
#[cfg(any(feature = "std", not(anyhow_no_core_error)))]
#[repr(transparent)]
pub struct BoxedError(pub Box<dyn StdError + Send + Sync>);
#[cfg(any(feature = "std", not(anyhow_no_core_error)))]
impl Debug for BoxedError {
fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
Debug::fmt(&self.0, f)
}
}
#[cfg(any(feature = "std", not(anyhow_no_core_error)))]
impl Display for BoxedError {
fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
Display::fmt(&self.0, f)
}
}
#[cfg(any(feature = "std", not(anyhow_no_core_error)))]
impl StdError for BoxedError {
fn source(&self) -> Option<&(dyn StdError + 'static)> {
self.0.source()
}
#[cfg(error_generic_member_access)]
fn provide<'a>(&'a self, request: &mut Request<'a>) {
nightly::provide(&*self.0, request);
}
}
+14
View File
@@ -0,0 +1,14 @@
use anyhow::{bail, Result};
use std::io;
pub fn bail_literal() -> Result<()> {
bail!("oh no!");
}
pub fn bail_fmt() -> Result<()> {
bail!("{} {}!", "oh", "no");
}
pub fn bail_error() -> Result<()> {
bail!(io::Error::new(io::ErrorKind::Other, "oh no!"));
}
+7
View File
@@ -0,0 +1,7 @@
#[rustversion::attr(not(nightly), ignore = "requires nightly")]
#[cfg_attr(miri, ignore = "incompatible with miri")]
#[test]
fn ui() {
let t = trybuild::TestCases::new();
t.compile_fail("tests/ui/*.rs");
}
+53
View File
@@ -0,0 +1,53 @@
#![allow(clippy::module_name_repetitions)]
use std::error::Error as StdError;
use std::fmt::{self, Display};
use std::sync::atomic::{AtomicBool, Ordering};
use std::sync::Arc;
#[derive(Debug)]
pub struct Flag {
atomic: Arc<AtomicBool>,
}
impl Flag {
pub fn new() -> Self {
Flag {
atomic: Arc::new(AtomicBool::new(false)),
}
}
pub fn get(&self) -> bool {
self.atomic.load(Ordering::Relaxed)
}
}
#[derive(Debug)]
pub struct DetectDrop {
has_dropped: Flag,
}
impl DetectDrop {
pub fn new(has_dropped: &Flag) -> Self {
DetectDrop {
has_dropped: Flag {
atomic: Arc::clone(&has_dropped.atomic),
},
}
}
}
impl StdError for DetectDrop {}
impl Display for DetectDrop {
fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
write!(f, "oh no!")
}
}
impl Drop for DetectDrop {
fn drop(&mut self) {
let already_dropped = self.has_dropped.atomic.swap(true, Ordering::Relaxed);
assert!(!already_dropped);
}
}
+34
View File
@@ -0,0 +1,34 @@
#![allow(clippy::extra_unused_type_parameters)]
use anyhow::Error;
use std::panic::{RefUnwindSafe, UnwindSafe};
#[test]
fn test_send() {
fn assert_send<T: Send>() {}
assert_send::<Error>();
}
#[test]
fn test_sync() {
fn assert_sync<T: Sync>() {}
assert_sync::<Error>();
}
#[test]
fn test_unwind_safe() {
fn assert_unwind_safe<T: UnwindSafe>() {}
assert_unwind_safe::<Error>();
}
#[test]
fn test_ref_unwind_safe() {
fn assert_ref_unwind_safe<T: RefUnwindSafe>() {}
assert_ref_unwind_safe::<Error>();
}
#[test]
fn test_unpin() {
fn assert_unpin<T: Unpin>() {}
assert_unpin::<Error>();
}
+15
View File
@@ -0,0 +1,15 @@
#![allow(clippy::let_underscore_untyped)]
#[rustversion::not(nightly)]
#[ignore = "requires nightly"]
#[test]
fn test_backtrace() {}
#[rustversion::nightly]
#[test]
fn test_backtrace() {
use anyhow::anyhow;
let error = anyhow!("oh no!");
let _ = error.backtrace();
}
+45
View File
@@ -0,0 +1,45 @@
#![allow(
// Clippy bug: https://github.com/rust-lang/rust-clippy/issues/7422
clippy::nonstandard_macro_braces,
)]
use anyhow::anyhow;
use std::error::Error as StdError;
use std::io;
use thiserror::Error;
#[derive(Error, Debug)]
#[error("outer")]
struct MyError {
source: io::Error,
}
#[test]
fn test_boxed_str() {
let error = Box::<dyn StdError + Send + Sync>::from("oh no!");
let error = anyhow!(error);
assert_eq!("oh no!", error.to_string());
assert_eq!(
"oh no!",
error
.downcast_ref::<Box<dyn StdError + Send + Sync>>()
.unwrap()
.to_string()
);
}
#[test]
fn test_boxed_thiserror() {
let error = MyError {
source: io::Error::new(io::ErrorKind::Other, "oh no!"),
};
let error = anyhow!(error);
assert_eq!("oh no!", error.source().unwrap().to_string());
}
#[test]
fn test_boxed_anyhow() {
let error = anyhow!("oh no!").context("it failed");
let error = anyhow!(error);
assert_eq!("oh no!", error.source().unwrap().to_string());
}
+69
View File
@@ -0,0 +1,69 @@
use anyhow::{anyhow, Chain, Error};
fn error() -> Error {
anyhow!({ 0 }).context(1).context(2).context(3)
}
#[test]
fn test_iter() {
let e = error();
let mut chain = e.chain();
assert_eq!("3", chain.next().unwrap().to_string());
assert_eq!("2", chain.next().unwrap().to_string());
assert_eq!("1", chain.next().unwrap().to_string());
assert_eq!("0", chain.next().unwrap().to_string());
assert!(chain.next().is_none());
assert!(chain.next_back().is_none());
}
#[test]
fn test_rev() {
let e = error();
let mut chain = e.chain().rev();
assert_eq!("0", chain.next().unwrap().to_string());
assert_eq!("1", chain.next().unwrap().to_string());
assert_eq!("2", chain.next().unwrap().to_string());
assert_eq!("3", chain.next().unwrap().to_string());
assert!(chain.next().is_none());
assert!(chain.next_back().is_none());
}
#[test]
fn test_len() {
let e = error();
let mut chain = e.chain();
assert_eq!(4, chain.len());
assert_eq!((4, Some(4)), chain.size_hint());
assert_eq!("3", chain.next().unwrap().to_string());
assert_eq!(3, chain.len());
assert_eq!((3, Some(3)), chain.size_hint());
assert_eq!("0", chain.next_back().unwrap().to_string());
assert_eq!(2, chain.len());
assert_eq!((2, Some(2)), chain.size_hint());
assert_eq!("2", chain.next().unwrap().to_string());
assert_eq!(1, chain.len());
assert_eq!((1, Some(1)), chain.size_hint());
assert_eq!("1", chain.next_back().unwrap().to_string());
assert_eq!(0, chain.len());
assert_eq!((0, Some(0)), chain.size_hint());
assert!(chain.next().is_none());
}
#[test]
fn test_default() {
let mut c = Chain::default();
assert!(c.next().is_none());
}
#[test]
#[allow(clippy::redundant_clone)]
fn test_clone() {
let e = error();
let mut chain = e.chain().clone();
assert_eq!("3", chain.next().unwrap().to_string());
assert_eq!("2", chain.next().unwrap().to_string());
assert_eq!("1", chain.next().unwrap().to_string());
assert_eq!("0", chain.next().unwrap().to_string());
assert!(chain.next().is_none());
assert!(chain.next_back().is_none());
}
+172
View File
@@ -0,0 +1,172 @@
#![allow(
// Clippy bug: https://github.com/rust-lang/rust-clippy/issues/7422
clippy::nonstandard_macro_braces,
)]
mod drop;
use crate::drop::{DetectDrop, Flag};
use anyhow::{Context, Error, Result};
use std::fmt::{self, Display};
use thiserror::Error;
// https://github.com/dtolnay/anyhow/issues/18
#[test]
fn test_inference() -> Result<()> {
let x = "1";
let y: u32 = x.parse().context("...")?;
assert_eq!(y, 1);
Ok(())
}
macro_rules! context_type {
($name:ident) => {
#[derive(Debug)]
struct $name {
message: &'static str,
#[allow(dead_code)]
drop: DetectDrop,
}
impl Display for $name {
fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
f.write_str(self.message)
}
}
};
}
context_type!(HighLevel);
context_type!(MidLevel);
#[derive(Error, Debug)]
#[error("{message}")]
struct LowLevel {
message: &'static str,
drop: DetectDrop,
}
struct Dropped {
low: Flag,
mid: Flag,
high: Flag,
}
impl Dropped {
fn none(&self) -> bool {
!self.low.get() && !self.mid.get() && !self.high.get()
}
fn all(&self) -> bool {
self.low.get() && self.mid.get() && self.high.get()
}
}
fn make_chain() -> (Error, Dropped) {
let dropped = Dropped {
low: Flag::new(),
mid: Flag::new(),
high: Flag::new(),
};
let low = LowLevel {
message: "no such file or directory",
drop: DetectDrop::new(&dropped.low),
};
// impl Context for Result<T, E>
let mid = Err::<(), LowLevel>(low)
.context(MidLevel {
message: "failed to load config",
drop: DetectDrop::new(&dropped.mid),
})
.unwrap_err();
// impl Context for Result<T, Error>
let high = Err::<(), Error>(mid)
.context(HighLevel {
message: "failed to start server",
drop: DetectDrop::new(&dropped.high),
})
.unwrap_err();
(high, dropped)
}
#[test]
fn test_downcast_ref() {
let (err, dropped) = make_chain();
assert!(!err.is::<String>());
assert!(err.downcast_ref::<String>().is_none());
assert!(err.is::<HighLevel>());
let high = err.downcast_ref::<HighLevel>().unwrap();
assert_eq!(high.to_string(), "failed to start server");
assert!(err.is::<MidLevel>());
let mid = err.downcast_ref::<MidLevel>().unwrap();
assert_eq!(mid.to_string(), "failed to load config");
assert!(err.is::<LowLevel>());
let low = err.downcast_ref::<LowLevel>().unwrap();
assert_eq!(low.to_string(), "no such file or directory");
assert!(dropped.none());
drop(err);
assert!(dropped.all());
}
#[test]
fn test_downcast_high() {
let (err, dropped) = make_chain();
let err = err.downcast::<HighLevel>().unwrap();
assert!(!dropped.high.get());
assert!(dropped.low.get() && dropped.mid.get());
drop(err);
assert!(dropped.all());
}
#[test]
fn test_downcast_mid() {
let (err, dropped) = make_chain();
let err = err.downcast::<MidLevel>().unwrap();
assert!(!dropped.mid.get());
assert!(dropped.low.get() && dropped.high.get());
drop(err);
assert!(dropped.all());
}
#[test]
fn test_downcast_low() {
let (err, dropped) = make_chain();
let err = err.downcast::<LowLevel>().unwrap();
assert!(!dropped.low.get());
assert!(dropped.mid.get() && dropped.high.get());
drop(err);
assert!(dropped.all());
}
#[test]
fn test_unsuccessful_downcast() {
let (err, dropped) = make_chain();
let err = err.downcast::<String>().unwrap_err();
assert!(dropped.none());
drop(err);
assert!(dropped.all());
}
#[test]
fn test_root_cause() {
let (err, _) = make_chain();
assert_eq!(err.root_cause().to_string(), "no such file or directory");
}
+46
View File
@@ -0,0 +1,46 @@
#![allow(clippy::unnecessary_wraps)]
mod drop;
use self::drop::{DetectDrop, Flag};
use anyhow::{Error, Result};
use std::error::Error as StdError;
#[test]
fn test_convert() {
let has_dropped = Flag::new();
let error = Error::new(DetectDrop::new(&has_dropped));
let box_dyn = Box::<dyn StdError>::from(error);
assert_eq!("oh no!", box_dyn.to_string());
drop(box_dyn);
assert!(has_dropped.get());
}
#[test]
fn test_convert_send() {
let has_dropped = Flag::new();
let error = Error::new(DetectDrop::new(&has_dropped));
let box_dyn = Box::<dyn StdError + Send>::from(error);
assert_eq!("oh no!", box_dyn.to_string());
drop(box_dyn);
assert!(has_dropped.get());
}
#[test]
fn test_convert_send_sync() {
let has_dropped = Flag::new();
let error = Error::new(DetectDrop::new(&has_dropped));
let box_dyn = Box::<dyn StdError + Send + Sync>::from(error);
assert_eq!("oh no!", box_dyn.to_string());
drop(box_dyn);
assert!(has_dropped.get());
}
#[test]
fn test_question_mark() -> Result<(), Box<dyn StdError>> {
fn f() -> Result<()> {
Ok(())
}
f()?;
Ok(())
}
+123
View File
@@ -0,0 +1,123 @@
#![allow(clippy::assertions_on_result_states, clippy::wildcard_imports)]
mod common;
mod drop;
use self::common::*;
use self::drop::{DetectDrop, Flag};
use anyhow::Error;
use std::error::Error as StdError;
use std::fmt::{self, Display};
use std::io;
#[test]
fn test_downcast() {
assert_eq!(
"oh no!",
bail_literal().unwrap_err().downcast::<&str>().unwrap(),
);
assert_eq!(
"oh no!",
bail_fmt().unwrap_err().downcast::<String>().unwrap(),
);
assert_eq!(
"oh no!",
bail_error()
.unwrap_err()
.downcast::<io::Error>()
.unwrap()
.to_string(),
);
}
#[test]
fn test_downcast_ref() {
assert_eq!(
"oh no!",
*bail_literal().unwrap_err().downcast_ref::<&str>().unwrap(),
);
assert_eq!(
"oh no!",
bail_fmt().unwrap_err().downcast_ref::<String>().unwrap(),
);
assert_eq!(
"oh no!",
bail_error()
.unwrap_err()
.downcast_ref::<io::Error>()
.unwrap()
.to_string(),
);
}
#[test]
fn test_downcast_mut() {
assert_eq!(
"oh no!",
*bail_literal().unwrap_err().downcast_mut::<&str>().unwrap(),
);
assert_eq!(
"oh no!",
bail_fmt().unwrap_err().downcast_mut::<String>().unwrap(),
);
assert_eq!(
"oh no!",
bail_error()
.unwrap_err()
.downcast_mut::<io::Error>()
.unwrap()
.to_string(),
);
let mut bailed = bail_fmt().unwrap_err();
*bailed.downcast_mut::<String>().unwrap() = "clobber".to_string();
assert_eq!(bailed.downcast_ref::<String>().unwrap(), "clobber");
assert_eq!(bailed.downcast_mut::<String>().unwrap(), "clobber");
assert_eq!(bailed.downcast::<String>().unwrap(), "clobber");
}
#[test]
fn test_drop() {
let has_dropped = Flag::new();
let error = Error::new(DetectDrop::new(&has_dropped));
drop(error.downcast::<DetectDrop>().unwrap());
assert!(has_dropped.get());
}
#[test]
fn test_as_ref() {
let error = bail_error().unwrap_err();
let ref_dyn: &dyn StdError = error.as_ref();
assert_eq!("oh no!", ref_dyn.to_string());
let ref_dyn_send_sync: &(dyn StdError + Send + Sync) = error.as_ref();
assert_eq!("oh no!", ref_dyn_send_sync.to_string());
}
#[test]
fn test_large_alignment() {
#[repr(align(64))]
#[derive(Debug)]
struct LargeAlignedError(&'static str);
impl Display for LargeAlignedError {
fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
f.write_str(self.0)
}
}
impl StdError for LargeAlignedError {}
let error = Error::new(LargeAlignedError("oh no!"));
assert_eq!(
"oh no!",
error.downcast_ref::<LargeAlignedError>().unwrap().0
);
}
#[test]
fn test_unsuccessful_downcast() {
let mut error = bail_error().unwrap_err();
assert!(error.downcast_ref::<&str>().is_none());
assert!(error.downcast_mut::<&str>().is_none());
assert!(error.downcast::<&str>().is_err());
}
+756
View File
@@ -0,0 +1,756 @@
#![allow(
clippy::bool_to_int_with_if,
clippy::char_lit_as_u8,
clippy::deref_addrof,
clippy::diverging_sub_expression,
clippy::erasing_op,
clippy::extra_unused_type_parameters,
clippy::if_same_then_else,
clippy::ifs_same_cond,
clippy::ignored_unit_patterns,
clippy::items_after_statements,
clippy::let_and_return,
clippy::let_underscore_untyped,
clippy::literal_string_with_formatting_args,
clippy::match_bool,
clippy::needless_else,
clippy::never_loop,
clippy::overly_complex_bool_expr,
clippy::ptr_cast_constness,
clippy::redundant_closure_call,
clippy::redundant_pattern_matching,
clippy::too_many_lines,
clippy::uninlined_format_args,
clippy::unit_arg,
clippy::unnecessary_cast,
clippy::while_immutable_condition,
clippy::zero_ptr,
irrefutable_let_patterns
)]
use self::Enum::Generic;
use anyhow::{anyhow, ensure, Chain, Error, Result};
use std::fmt::{self, Debug};
use std::iter;
use std::marker::{PhantomData, PhantomData as P};
use std::mem;
use std::ops::Add;
use std::ptr;
struct S;
impl<T> Add<T> for S {
type Output = bool;
fn add(self, rhs: T) -> Self::Output {
let _ = rhs;
false
}
}
trait Trait: Sized {
const V: usize = 0;
fn t(self, i: i32) -> i32 {
i
}
}
impl<T> Trait for T {}
enum Enum<T: ?Sized> {
#[allow(dead_code)]
Thing(PhantomData<T>),
Generic,
}
impl<T: ?Sized> PartialEq for Enum<T> {
fn eq(&self, rhs: &Self) -> bool {
mem::discriminant(self) == mem::discriminant(rhs)
}
}
impl<T: ?Sized> Debug for Enum<T> {
fn fmt(&self, formatter: &mut fmt::Formatter) -> fmt::Result {
formatter.write_str("Generic")
}
}
#[track_caller]
fn assert_err<T: Debug>(result: impl FnOnce() -> Result<T>, expected: &'static str) {
let actual = result().unwrap_err().to_string();
// In general different rustc versions will format the interpolated lhs and
// rhs $:expr fragment with insignificant differences in whitespace or
// punctuation, so we check the message in full against nightly and do just
// a cursory test on older toolchains.
if rustversion::cfg!(nightly) && !cfg!(miri) {
assert_eq!(actual, expected);
} else {
assert_eq!(actual.contains(" vs "), expected.contains(" vs "));
}
}
#[test]
fn test_recursion() {
// Must not blow the default #[recursion_limit], which is 128.
#[rustfmt::skip]
let test = || Ok(ensure!(
false | false | false | false | false | false | false | false | false |
false | false | false | false | false | false | false | false | false |
false | false | false | false | false | false | false | false | false |
false | false | false | false | false | false | false | false | false |
false | false | false | false | false | false | false | false | false |
false | false | false | false | false | false | false | false | false |
false | false | false | false | false | false | false | false | false
));
test().unwrap_err();
}
#[test]
fn test_low_precedence_control_flow() {
#[allow(unreachable_code)]
let test = || {
let val = loop {
// Break has lower precedence than the comparison operators so the
// expression here is `S + (break (1 == 1))`. It would be bad if the
// ensure macro partitioned this input into `(S + break 1) == (1)`
// because that means a different thing than what was written.
ensure!(S + break 1 == 1);
};
Ok(val)
};
assert!(test().unwrap());
}
#[test]
fn test_low_precedence_binary_operator() {
// Must not partition as `false == (true && false)`.
let test = || Ok(ensure!(false == true && false));
assert_err(test, "Condition failed: `false == true && false`");
// But outside the root level, it is fine.
let test = || Ok(ensure!(while false == true && false {} < ()));
assert_err(
test,
"Condition failed: `while false == true && false {} < ()` (() vs ())",
);
let a = 15;
let b = 3;
let test = || Ok(ensure!(a <= b || a - b <= 10));
assert_err(test, "Condition failed: `a <= b || a - b <= 10`");
}
#[test]
fn test_high_precedence_binary_operator() {
let a = 15;
let b = 3;
let test = || Ok(ensure!(a - b <= 10));
assert_err(test, "Condition failed: `a - b <= 10` (12 vs 10)");
}
#[test]
fn test_closure() {
// Must not partition as `(S + move) || (1 == 1)` by treating move as an
// identifier, nor as `(S + move || 1) == (1)` by misinterpreting the
// closure precedence.
let test = || Ok(ensure!(S + move || 1 == 1));
assert_err(test, "Condition failed: `S + move || 1 == 1`");
let test = || Ok(ensure!(S + || 1 == 1));
assert_err(test, "Condition failed: `S + || 1 == 1`");
// Must not partition as `S + ((move | ()) | 1) == 1` by treating those
// pipes as bitwise-or.
let test = || Ok(ensure!(S + move |()| 1 == 1));
assert_err(test, "Condition failed: `S + move |()| 1 == 1`");
let test = || Ok(ensure!(S + |()| 1 == 1));
assert_err(test, "Condition failed: `S + |()| 1 == 1`");
}
#[test]
fn test_unary() {
let mut x = &1;
let test = || Ok(ensure!(*x == 2));
assert_err(test, "Condition failed: `*x == 2` (1 vs 2)");
let test = || Ok(ensure!(!x == 1));
assert_err(test, "Condition failed: `!x == 1` (-2 vs 1)");
let test = || Ok(ensure!(-x == 1));
assert_err(test, "Condition failed: `-x == 1` (-1 vs 1)");
let test = || Ok(ensure!(&x == &&2));
assert_err(test, "Condition failed: `&x == &&2` (1 vs 2)");
let test = || Ok(ensure!(&mut x == *&&mut &2));
assert_err(test, "Condition failed: `&mut x == *&&mut &2` (1 vs 2)");
}
#[rustversion::since(1.82)]
#[test]
fn test_raw_addr() {
let mut x = 1;
let test = || Ok(ensure!(S + &raw const x != S + &raw mut x));
assert_err(
test,
"Condition failed: `S + &raw const x != S + &raw mut x` (false vs false)",
);
}
#[test]
fn test_if() {
#[rustfmt::skip]
let test = || Ok(ensure!(if false {}.t(1) == 2));
assert_err(test, "Condition failed: `if false {}.t(1) == 2` (1 vs 2)");
#[rustfmt::skip]
let test = || Ok(ensure!(if false {} else {}.t(1) == 2));
assert_err(
test,
"Condition failed: `if false {} else {}.t(1) == 2` (1 vs 2)",
);
#[rustfmt::skip]
let test = || Ok(ensure!(if false {} else if false {}.t(1) == 2));
assert_err(
test,
"Condition failed: `if false {} else if false {}.t(1) == 2` (1 vs 2)",
);
#[rustfmt::skip]
let test = || Ok(ensure!(if let 1 = 2 {}.t(1) == 2));
assert_err(
test,
"Condition failed: `if let 1 = 2 {}.t(1) == 2` (1 vs 2)",
);
#[rustfmt::skip]
let test = || Ok(ensure!(if let 1 | 2 = 2 {}.t(1) == 2));
assert_err(
test,
"Condition failed: `if let 1 | 2 = 2 {}.t(1) == 2` (1 vs 2)",
);
#[rustfmt::skip]
let test = || Ok(ensure!(if let | 1 | 2 = 2 {}.t(1) == 2));
assert_err(
test,
"Condition failed: `if let | 1 | 2 = 2 {}.t(1) == 2` (1 vs 2)",
);
}
#[test]
fn test_loop() {
#[rustfmt::skip]
let test = || Ok(ensure!(1 + loop { break 1 } == 1));
assert_err(
test,
"Condition failed: `1 + loop { break 1 } == 1` (2 vs 1)",
);
#[rustfmt::skip]
let test = || Ok(ensure!(1 + 'a: loop { break 'a 1 } == 1));
assert_err(
test,
"Condition failed: `1 + 'a: loop { break 'a 1 } == 1` (2 vs 1)",
);
#[rustfmt::skip]
let test = || Ok(ensure!(while false {}.t(1) == 2));
assert_err(
test,
"Condition failed: `while false {}.t(1) == 2` (1 vs 2)",
);
#[rustfmt::skip]
let test = || Ok(ensure!(while let None = Some(1) {}.t(1) == 2));
assert_err(
test,
"Condition failed: `while let None = Some(1) {}.t(1) == 2` (1 vs 2)",
);
#[rustfmt::skip]
let test = || Ok(ensure!(for _x in iter::once(0) {}.t(1) == 2));
assert_err(
test,
"Condition failed: `for _x in iter::once(0) {}.t(1) == 2` (1 vs 2)",
);
#[rustfmt::skip]
let test = || Ok(ensure!(for | _x in iter::once(0) {}.t(1) == 2));
assert_err(
test,
"Condition failed: `for | _x in iter::once(0) {}.t(1) == 2` (1 vs 2)",
);
#[rustfmt::skip]
let test = || Ok(ensure!(for true | false in iter::empty() {}.t(1) == 2));
assert_err(
test,
"Condition failed: `for true | false in iter::empty() {}.t(1) == 2` (1 vs 2)",
);
}
#[test]
fn test_match() {
#[rustfmt::skip]
let test = || Ok(ensure!(match 1 == 1 { true => 1, false => 0 } == 2));
assert_err(
test,
"Condition failed: `match 1 == 1 { true => 1, false => 0 } == 2` (1 vs 2)",
);
}
#[test]
fn test_atom() {
let test = || Ok(ensure!([false, false].len() > 3));
assert_err(
test,
"Condition failed: `[false, false].len() > 3` (2 vs 3)",
);
#[rustfmt::skip]
let test = || Ok(ensure!({ let x = 1; x } >= 3));
assert_err(test, "Condition failed: `{ let x = 1; x } >= 3` (1 vs 3)");
let test = || Ok(ensure!(S + async { 1 } == true));
assert_err(
test,
"Condition failed: `S + async { 1 } == true` (false vs true)",
);
let test = || Ok(ensure!(S + async move { 1 } == true));
assert_err(
test,
"Condition failed: `S + async move { 1 } == true` (false vs true)",
);
let x = &1;
let test = || Ok(ensure!(S + unsafe { ptr::read(x) } == true));
assert_err(
test,
"Condition failed: `S + unsafe { ptr::read(x) } == true` (false vs true)",
);
}
#[test]
fn test_path() {
let test = || Ok(ensure!(crate::S.t(1) == 2));
assert_err(test, "Condition failed: `crate::S.t(1) == 2` (1 vs 2)");
let test = || Ok(ensure!(::anyhow::Error::root_cause.t(1) == 2));
assert_err(
test,
"Condition failed: `::anyhow::Error::root_cause.t(1) == 2` (1 vs 2)",
);
let test = || Ok(ensure!(Error::msg::<&str>.t(1) == 2));
assert_err(
test,
"Condition failed: `Error::msg::<&str>.t(1) == 2` (1 vs 2)",
);
#[rustfmt::skip]
let test = || Ok(ensure!(Error::msg::<&str,>.t(1) == 2));
assert_err(
test,
"Condition failed: `Error::msg::<&str,>.t(1) == 2` (1 vs 2)",
);
let test = || Ok(ensure!(Error::msg::<<str as ToOwned>::Owned>.t(1) == 2));
assert_err(
test,
"Condition failed: `Error::msg::<<str as ToOwned>::Owned>.t(1) == 2` (1 vs 2)",
);
let test = || Ok(ensure!(Chain::<'static>::new.t(1) == 2));
assert_err(
test,
"Condition failed: `Chain::<'static>::new.t(1) == 2` (1 vs 2)",
);
#[rustfmt::skip]
let test = || Ok(ensure!(Chain::<'static,>::new.t(1) == 2));
assert_err(
test,
"Condition failed: `Chain::<'static,>::new.t(1) == 2` (1 vs 2)",
);
fn f<const I: isize>() {}
let test = || Ok(ensure!(f::<1>() != ()));
assert_err(test, "Condition failed: `f::<1>() != ()` (() vs ())");
let test = || Ok(ensure!(f::<-1>() != ()));
assert_err(test, "Condition failed: `f::<-1>() != ()` (() vs ())");
fn g<T, const I: isize>() {}
let test = || Ok(ensure!(g::<u8, 1>() != ()));
assert_err(test, "Condition failed: `g::<u8, 1>() != ()` (() vs ())");
let test = || Ok(ensure!(g::<u8, -1>() != ()));
assert_err(test, "Condition failed: `g::<u8, -1>() != ()` (() vs ())");
#[derive(PartialOrd, PartialEq, Debug)]
enum E<'a, T> {
#[allow(dead_code)]
T(&'a T),
U,
}
#[rustfmt::skip]
let test = || Ok(ensure!(E::U::<>>E::U::<u8>));
assert_err(test, "Condition failed: `E::U::<> > E::U::<u8>` (U vs U)");
#[rustfmt::skip]
let test = || Ok(ensure!(E::U::<u8>>E::U));
assert_err(test, "Condition failed: `E::U::<u8> > E::U` (U vs U)");
#[rustfmt::skip]
let test = || Ok(ensure!(E::U::<u8,>>E::U));
assert_err(test, "Condition failed: `E::U::<u8,> > E::U` (U vs U)");
let test = || Ok(ensure!(Generic::<dyn Debug + Sync> != Generic));
assert_err(
test,
"Condition failed: `Generic::<dyn Debug + Sync> != Generic` (Generic vs Generic)",
);
let test = || Ok(ensure!(Generic::<dyn Fn() + Sync> != Generic));
assert_err(
test,
"Condition failed: `Generic::<dyn Fn() + Sync> != Generic` (Generic vs Generic)",
);
#[rustfmt::skip]
let test = || {
Ok(ensure!(
Generic::<dyn Fn::() + ::std::marker::Sync> != Generic
))
};
assert_err(
test,
"Condition failed: `Generic::<dyn Fn::() + ::std::marker::Sync> != Generic` (Generic vs Generic)",
);
}
#[test]
fn test_macro() {
let test = || Ok(ensure!(anyhow!("...").to_string().len() <= 1));
assert_err(
test,
"Condition failed: `anyhow!(\"...\").to_string().len() <= 1` (3 vs 1)",
);
let test = || Ok(ensure!(vec![1].len() < 1));
assert_err(test, "Condition failed: `vec![1].len() < 1` (1 vs 1)");
let test = || Ok(ensure!(stringify! {} != ""));
assert_err(
test,
"Condition failed: `stringify! {} != \"\"` (\"\" vs \"\")",
);
}
#[test]
fn test_trailer() {
let test = || Ok(ensure!((|| 1)() == 2));
assert_err(test, "Condition failed: `(|| 1)() == 2` (1 vs 2)");
let test = || Ok(ensure!(b"hmm"[1] == b'c'));
assert_err(test, "Condition failed: `b\"hmm\"[1] == b'c'` (109 vs 99)");
let test = || Ok(ensure!(PhantomData::<u8> {} != PhantomData));
assert_err(
test,
"Condition failed: `PhantomData::<u8> {} != PhantomData` (PhantomData<u8> vs PhantomData<u8>)",
);
let result = Ok::<_, Error>(1);
let test = || Ok(ensure!(result? == 2));
assert_err(test, "Condition failed: `result? == 2` (1 vs 2)");
let test = || Ok(ensure!((2, 3).1 == 2));
assert_err(test, "Condition failed: `(2, 3).1 == 2` (3 vs 2)");
#[rustfmt::skip]
let test = || Ok(ensure!((2, (3, 4)). 1.1 == 2));
assert_err(test, "Condition failed: `(2, (3, 4)).1.1 == 2` (4 vs 2)");
let err = anyhow!("");
let test = || Ok(ensure!(err.is::<&str>() == false));
assert_err(
test,
"Condition failed: `err.is::<&str>() == false` (true vs false)",
);
let test = || Ok(ensure!(err.is::<<str as ToOwned>::Owned>() == true));
assert_err(
test,
"Condition failed: `err.is::<<str as ToOwned>::Owned>() == true` (false vs true)",
);
}
#[test]
fn test_whitespace() {
#[derive(Debug)]
pub struct Point {
#[allow(dead_code)]
pub x: i32,
#[allow(dead_code)]
pub y: i32,
}
let point = Point { x: 0, y: 0 };
let test = || Ok(ensure!("" == format!("{:#?}", point)));
assert_err(
test,
"Condition failed: `\"\" == format!(\"{:#?}\", point)`",
);
}
#[test]
fn test_too_long() {
let test = || Ok(ensure!("" == "x".repeat(10)));
assert_err(
test,
"Condition failed: `\"\" == \"x\".repeat(10)` (\"\" vs \"xxxxxxxxxx\")",
);
let test = || Ok(ensure!("" == "x".repeat(80)));
assert_err(test, "Condition failed: `\"\" == \"x\".repeat(80)`");
}
#[test]
fn test_as() {
let test = || Ok(ensure!('\0' as u8 > 1));
assert_err(test, "Condition failed: `'\\0' as u8 > 1` (0 vs 1)");
let test = || Ok(ensure!('\0' as ::std::primitive::u8 > 1));
assert_err(
test,
"Condition failed: `'\\0' as ::std::primitive::u8 > 1` (0 vs 1)",
);
let test = || Ok(ensure!(&[0] as &[i32] == [1]));
assert_err(
test,
"Condition failed: `&[0] as &[i32] == [1]` ([0] vs [1])",
);
let test = || Ok(ensure!(0 as *const () as *mut _ == 1 as *mut ()));
assert_err(
test,
"Condition failed: `0 as *const () as *mut _ == 1 as *mut ()` (0x0 vs 0x1)",
);
let s = "";
let test = || Ok(ensure!(s as &str != s));
assert_err(test, "Condition failed: `s as &str != s` (\"\" vs \"\")");
let test = || Ok(ensure!(&s as &&str != &s));
assert_err(test, "Condition failed: `&s as &&str != &s` (\"\" vs \"\")");
let test = || Ok(ensure!(s as &'static str != s));
assert_err(
test,
"Condition failed: `s as &'static str != s` (\"\" vs \"\")",
);
let test = || Ok(ensure!(&s as &&'static str != &s));
assert_err(
test,
"Condition failed: `&s as &&'static str != &s` (\"\" vs \"\")",
);
let m: &mut str = Default::default();
let test = || Ok(ensure!(m as &mut str != s));
assert_err(
test,
"Condition failed: `m as &mut str != s` (\"\" vs \"\")",
);
let test = || Ok(ensure!(&m as &&mut str != &s));
assert_err(
test,
"Condition failed: `&m as &&mut str != &s` (\"\" vs \"\")",
);
let test = || Ok(ensure!(&m as &&'static mut str != &s));
assert_err(
test,
"Condition failed: `&m as &&'static mut str != &s` (\"\" vs \"\")",
);
let f = || {};
let test = || Ok(ensure!(f as fn() as usize * 0 != 0));
assert_err(
test,
"Condition failed: `f as fn() as usize * 0 != 0` (0 vs 0)",
);
let test = || Ok(ensure!(f as fn() -> () as usize * 0 != 0));
assert_err(
test,
"Condition failed: `f as fn() -> () as usize * 0 != 0` (0 vs 0)",
);
let test = || Ok(ensure!(f as for<'a> fn() as usize * 0 != 0));
assert_err(
test,
"Condition failed: `f as for<'a> fn() as usize * 0 != 0` (0 vs 0)",
);
let test = || Ok(ensure!(f as unsafe fn() as usize * 0 != 0));
assert_err(
test,
"Condition failed: `f as unsafe fn() as usize * 0 != 0` (0 vs 0)",
);
#[rustfmt::skip]
let test = || Ok(ensure!(f as extern "Rust" fn() as usize * 0 != 0));
assert_err(
test,
"Condition failed: `f as extern \"Rust\" fn() as usize * 0 != 0` (0 vs 0)",
);
extern "C" fn extern_fn() {}
#[rustfmt::skip]
#[allow(missing_abi)]
let test = || Ok(ensure!(extern_fn as extern fn() as usize * 0 != 0));
assert_err(
test,
"Condition failed: `extern_fn as extern fn() as usize * 0 != 0` (0 vs 0)",
);
let f = || -> ! { panic!() };
let test = || Ok(ensure!(f as fn() -> ! as usize * 0 != 0));
assert_err(
test,
"Condition failed: `f as fn() -> ! as usize * 0 != 0` (0 vs 0)",
);
trait EqDebug<T>: PartialEq<T> + Debug {
type Assoc;
}
impl<S, T> EqDebug<T> for S
where
S: PartialEq<T> + Debug,
{
type Assoc = bool;
}
let test = || Ok(ensure!(&0 as &dyn EqDebug<i32, Assoc = bool> != &0));
assert_err(
test,
"Condition failed: `&0 as &dyn EqDebug<i32, Assoc = bool> != &0` (0 vs 0)",
);
let test = || {
Ok(ensure!(
PhantomData as PhantomData<<i32 as ToOwned>::Owned> != PhantomData
))
};
assert_err(
test,
"Condition failed: `PhantomData as PhantomData<<i32 as ToOwned>::Owned> != PhantomData` (PhantomData<i32> vs PhantomData<i32>)",
);
macro_rules! int {
(...) => {
u8
};
}
let test = || Ok(ensure!(0 as int!(...) != 0));
assert_err(test, "Condition failed: `0 as int!(...) != 0` (0 vs 0)");
let test = || Ok(ensure!(0 as int![...] != 0));
assert_err(test, "Condition failed: `0 as int![...] != 0` (0 vs 0)");
let test = || Ok(ensure!(0 as int! {...} != 0));
assert_err(test, "Condition failed: `0 as int! {...} != 0` (0 vs 0)");
}
#[test]
fn test_pat() {
let test = || Ok(ensure!(if let ref mut _x @ 0 = 0 { 0 } else { 1 } == 1));
assert_err(
test,
"Condition failed: `if let ref mut _x @ 0 = 0 { 0 } else { 1 } == 1` (0 vs 1)",
);
let test = || Ok(ensure!(if let -1..=1 = 0 { 0 } else { 1 } == 1));
assert_err(
test,
"Condition failed: `if let -1..=1 = 0 { 0 } else { 1 } == 1` (0 vs 1)",
);
let test = || Ok(ensure!(if let &0 = &0 { 0 } else { 1 } == 1));
assert_err(
test,
"Condition failed: `if let &0 = &0 { 0 } else { 1 } == 1` (0 vs 1)",
);
let test = || Ok(ensure!(if let &&0 = &&0 { 0 } else { 1 } == 1));
assert_err(
test,
"Condition failed: `if let &&0 = &&0 { 0 } else { 1 } == 1` (0 vs 1)",
);
let test = || Ok(ensure!(if let &mut 0 = &mut 0 { 0 } else { 1 } == 1));
assert_err(
test,
"Condition failed: `if let &mut 0 = &mut 0 { 0 } else { 1 } == 1` (0 vs 1)",
);
let test = || Ok(ensure!(if let &&mut 0 = &&mut 0 { 0 } else { 1 } == 1));
assert_err(
test,
"Condition failed: `if let &&mut 0 = &&mut 0 { 0 } else { 1 } == 1` (0 vs 1)",
);
let test = || Ok(ensure!(if let (0, 1) = (0, 1) { 0 } else { 1 } == 1));
assert_err(
test,
"Condition failed: `if let (0, 1) = (0, 1) { 0 } else { 1 } == 1` (0 vs 1)",
);
let test = || Ok(ensure!(if let [0] = b"\0" { 0 } else { 1 } == 1));
assert_err(
test,
"Condition failed: `if let [0] = b\"\\0\" { 0 } else { 1 } == 1` (0 vs 1)",
);
let p = PhantomData::<u8>;
let test = || Ok(ensure!(if let P::<u8> {} = p { 0 } else { 1 } == 1));
assert_err(
test,
"Condition failed: `if let P::<u8> {} = p { 0 } else { 1 } == 1` (0 vs 1)",
);
let test = || Ok(ensure!(if let ::std::marker::PhantomData = p {} != ()));
assert_err(
test,
"Condition failed: `if let ::std::marker::PhantomData = p {} != ()` (() vs ())",
);
let test = || Ok(ensure!(if let <S as Trait>::V = 0 { 0 } else { 1 } == 1));
assert_err(
test,
"Condition failed: `if let <S as Trait>::V = 0 { 0 } else { 1 } == 1` (0 vs 1)",
);
let test = || Ok(ensure!(for _ in iter::once(()) {} != ()));
assert_err(
test,
"Condition failed: `for _ in iter::once(()) {} != ()` (() vs ())",
);
let test = || Ok(ensure!(if let stringify!(x) = "x" { 0 } else { 1 } == 1));
assert_err(
test,
"Condition failed: `if let stringify!(x) = \"x\" { 0 } else { 1 } == 1` (0 vs 1)",
);
}
+19
View File
@@ -0,0 +1,19 @@
#![deny(improper_ctypes, improper_ctypes_definitions)]
#![allow(clippy::uninlined_format_args)]
use anyhow::anyhow;
#[no_mangle]
pub extern "C" fn anyhow1(err: anyhow::Error) {
println!("{:?}", err);
}
#[no_mangle]
pub extern "C" fn anyhow2(err: &mut Option<anyhow::Error>) {
*err = Some(anyhow!("ffi error"));
}
#[no_mangle]
pub extern "C" fn anyhow3() -> Option<anyhow::Error> {
Some(anyhow!("ffi error"))
}
+93
View File
@@ -0,0 +1,93 @@
use anyhow::{bail, Context, Result};
use std::io;
fn f() -> Result<()> {
bail!(io::Error::new(io::ErrorKind::PermissionDenied, "oh no!"));
}
fn g() -> Result<()> {
f().context("f failed")
}
fn h() -> Result<()> {
g().context("g failed")
}
const EXPECTED_ALTDISPLAY_F: &str = "oh no!";
const EXPECTED_ALTDISPLAY_G: &str = "f failed: oh no!";
const EXPECTED_ALTDISPLAY_H: &str = "g failed: f failed: oh no!";
const EXPECTED_DEBUG_F: &str = "oh no!";
const EXPECTED_DEBUG_G: &str = "\
f failed
Caused by:
oh no!\
";
const EXPECTED_DEBUG_H: &str = "\
g failed
Caused by:
0: f failed
1: oh no!\
";
const EXPECTED_ALTDEBUG_F: &str = "\
Custom {
kind: PermissionDenied,
error: \"oh no!\",
}\
";
const EXPECTED_ALTDEBUG_G: &str = "\
Error {
context: \"f failed\",
source: Custom {
kind: PermissionDenied,
error: \"oh no!\",
},
}\
";
const EXPECTED_ALTDEBUG_H: &str = "\
Error {
context: \"g failed\",
source: Error {
context: \"f failed\",
source: Custom {
kind: PermissionDenied,
error: \"oh no!\",
},
},
}\
";
#[test]
fn test_display() {
assert_eq!("g failed", h().unwrap_err().to_string());
}
#[test]
fn test_altdisplay() {
assert_eq!(EXPECTED_ALTDISPLAY_F, format!("{:#}", f().unwrap_err()));
assert_eq!(EXPECTED_ALTDISPLAY_G, format!("{:#}", g().unwrap_err()));
assert_eq!(EXPECTED_ALTDISPLAY_H, format!("{:#}", h().unwrap_err()));
}
#[test]
fn test_debug() {
assert_eq!(EXPECTED_DEBUG_F, format!("{:?}", f().unwrap_err()));
assert_eq!(EXPECTED_DEBUG_G, format!("{:?}", g().unwrap_err()));
assert_eq!(EXPECTED_DEBUG_H, format!("{:?}", h().unwrap_err()));
}
#[test]
fn test_altdebug() {
assert_eq!(EXPECTED_ALTDEBUG_F, format!("{:#?}", f().unwrap_err()));
assert_eq!(EXPECTED_ALTDEBUG_G, format!("{:#?}", g().unwrap_err()));
assert_eq!(EXPECTED_ALTDEBUG_H, format!("{:#?}", h().unwrap_err()));
}
+97
View File
@@ -0,0 +1,97 @@
#![allow(
clippy::assertions_on_result_states,
clippy::eq_op,
clippy::incompatible_msrv, // https://github.com/rust-lang/rust-clippy/issues/12257
clippy::items_after_statements,
clippy::match_single_binding,
clippy::needless_pass_by_value,
clippy::shadow_unrelated,
clippy::uninlined_format_args,
clippy::wildcard_imports
)]
mod common;
use self::common::*;
use anyhow::{anyhow, ensure, Result};
use std::cell::Cell;
use std::future;
#[test]
fn test_messages() {
assert_eq!("oh no!", bail_literal().unwrap_err().to_string());
assert_eq!("oh no!", bail_fmt().unwrap_err().to_string());
assert_eq!("oh no!", bail_error().unwrap_err().to_string());
}
#[test]
fn test_ensure() {
let f = || {
ensure!(1 + 1 == 2, "This is correct");
Ok(())
};
assert!(f().is_ok());
let v = 1;
let f = || {
ensure!(v + v == 2, "This is correct, v: {}", v);
Ok(())
};
assert!(f().is_ok());
let f = || {
ensure!(v + v == 1, "This is not correct, v: {}", v);
Ok(())
};
assert!(f().is_err());
let f = || {
ensure!(v + v == 1);
Ok(())
};
assert_eq!(
f().unwrap_err().to_string(),
"Condition failed: `v + v == 1` (2 vs 1)",
);
}
#[test]
fn test_ensure_nonbool() -> Result<()> {
struct Struct {
condition: bool,
}
let s = Struct { condition: true };
match &s {
Struct { condition } => ensure!(condition), // &bool
}
Ok(())
}
#[test]
fn test_temporaries() {
fn require_send_sync(_: impl Send + Sync) {}
require_send_sync(async {
// If anyhow hasn't dropped any temporary format_args it creates by the
// time it's done evaluating, those will stick around until the
// semicolon, which is on the other side of the await point, making the
// enclosing future non-Send.
future::ready(anyhow!("...")).await;
});
fn message(cell: Cell<&str>) -> &str {
cell.get()
}
require_send_sync(async {
future::ready(anyhow!(message(Cell::new("...")))).await;
});
}
#[test]
fn test_brace_escape() {
let err = anyhow!("unterminated ${{..}} expression");
assert_eq!("unterminated ${..} expression", err.to_string());
}
+30
View File
@@ -0,0 +1,30 @@
#![allow(clippy::extra_unused_type_parameters)]
mod drop;
use self::drop::{DetectDrop, Flag};
use anyhow::Error;
use std::mem;
#[test]
fn test_error_size() {
assert_eq!(mem::size_of::<Error>(), mem::size_of::<usize>());
}
#[test]
fn test_null_pointer_optimization() {
assert_eq!(mem::size_of::<Result<(), Error>>(), mem::size_of::<usize>());
}
#[test]
fn test_autotraits() {
fn assert<E: Unpin + Send + Sync + 'static>() {}
assert::<Error>();
}
#[test]
fn test_drop() {
let has_dropped = Flag::new();
drop(Error::new(DetectDrop::new(&has_dropped)));
assert!(has_dropped.get());
}
+62
View File
@@ -0,0 +1,62 @@
use anyhow::anyhow;
use std::error::Error as StdError;
use std::fmt::{self, Display};
use std::io;
#[derive(Debug)]
enum TestError {
Io(io::Error),
}
impl Display for TestError {
fn fmt(&self, formatter: &mut fmt::Formatter) -> fmt::Result {
match self {
TestError::Io(e) => Display::fmt(e, formatter),
}
}
}
impl StdError for TestError {
fn source(&self) -> Option<&(dyn StdError + 'static)> {
match self {
TestError::Io(io) => Some(io),
}
}
}
#[test]
fn test_literal_source() {
let error = anyhow!("oh no!");
assert!(error.source().is_none());
}
#[test]
fn test_variable_source() {
let msg = "oh no!";
let error = anyhow!(msg);
assert!(error.source().is_none());
let msg = msg.to_owned();
let error = anyhow!(msg);
assert!(error.source().is_none());
}
#[test]
fn test_fmt_source() {
let error = anyhow!("{} {}!", "oh", "no");
assert!(error.source().is_none());
}
#[test]
fn test_io_source() {
let io = io::Error::new(io::ErrorKind::Other, "oh no!");
let error = anyhow!(TestError::Io(io));
assert_eq!("oh no!", error.source().unwrap().to_string());
}
#[test]
fn test_anyhow_from_anyhow() {
let error = anyhow!("oh no!").context("context");
let error = anyhow!(error);
assert_eq!("oh no!", error.source().unwrap().to_string());
}
+8
View File
@@ -0,0 +1,8 @@
use anyhow::{ensure, Result};
fn main() -> Result<()> {
// `ensure!` must not partition this into `(false) == (false == true)`
// because Rust doesn't ordinarily allow this form of expression.
ensure!(false == false == true);
Ok(())
}
+10
View File
@@ -0,0 +1,10 @@
error: comparison operators cannot be chained
--> tests/ui/chained-comparison.rs:6:19
|
6 | ensure!(false == false == true);
| ^^ ^^
|
help: split the comparison into two
|
6 | ensure!(false == false && false == true);
| ++++++++
+6
View File
@@ -0,0 +1,6 @@
use anyhow::{ensure, Result};
fn main() -> Result<()> {
ensure!();
Ok(())
}
+12
View File
@@ -0,0 +1,12 @@
error: unexpected end of macro invocation
--> tests/ui/empty-ensure.rs:4:5
|
4 | ensure!();
| ^^^^^^^^^ missing tokens in macro arguments
|
note: while trying to match meta-variable `$cond:expr`
--> src/ensure.rs
|
| ($cond:expr $(,)?) => {
| ^^^^^^^^^^
= note: this error originates in the macro `$crate::__parse_ensure` which comes from the expansion of the macro `ensure` (in Nightly builds, run with -Z macro-backtrace for more info)
+40
View File
@@ -0,0 +1,40 @@
use anyhow::{ensure, Result};
use std::ops::{Deref, Not};
struct Bool(bool);
struct DerefBool(bool);
struct NotBool(bool);
impl Deref for DerefBool {
type Target = bool;
fn deref(&self) -> &Self::Target {
&self.0
}
}
impl Not for NotBool {
type Output = bool;
fn not(self) -> Self::Output {
!self.0
}
}
fn main() -> Result<()> {
ensure!("...");
let mut s = Bool(true);
match &mut s {
Bool(cond) => ensure!(cond),
}
let db = DerefBool(true);
ensure!(db);
ensure!(&db);
let nb = NotBool(true);
ensure!(nb);
Ok(())
}
+113
View File
@@ -0,0 +1,113 @@
error[E0277]: the trait bound `&str: anyhow::__private::not::Bool` is not satisfied
--> tests/ui/ensure-nonbool.rs:25:13
|
25 | ensure!("...");
| --------^^^^^-
| | |
| | the trait `anyhow::__private::not::Bool` is not implemented for `&str`
| required by a bound introduced by this call
|
help: the following other types implement trait `anyhow::__private::not::Bool`
--> src/lib.rs
|
| impl Bool for bool {
| ^^^^^^^^^^^^^^^^^^ `bool`
...
| impl Bool for &bool {
| ^^^^^^^^^^^^^^^^^^^ `&bool`
note: required by a bound in `anyhow::__private::not`
--> src/lib.rs
|
| pub fn not(cond: impl Bool) -> bool {
| ^^^^ required by this bound in `not`
error[E0277]: the trait bound `&mut bool: anyhow::__private::not::Bool` is not satisfied
--> tests/ui/ensure-nonbool.rs:29:31
|
29 | Bool(cond) => ensure!(cond),
| --------^^^^-
| | |
| | the trait `anyhow::__private::not::Bool` is not implemented for `&mut bool`
| required by a bound introduced by this call
|
= note: `anyhow::__private::not::Bool` is implemented for `&bool`, but not for `&mut bool`
note: required by a bound in `anyhow::__private::not`
--> src/lib.rs
|
| pub fn not(cond: impl Bool) -> bool {
| ^^^^ required by this bound in `not`
help: consider dereferencing here
|
29 | Bool(cond) => ensure!(*cond),
| +
error[E0277]: the trait bound `DerefBool: anyhow::__private::not::Bool` is not satisfied
--> tests/ui/ensure-nonbool.rs:33:13
|
33 | ensure!(db);
| --------^^-
| | |
| | unsatisfied trait bound
| required by a bound introduced by this call
|
help: the trait `anyhow::__private::not::Bool` is not implemented for `DerefBool`
--> tests/ui/ensure-nonbool.rs:6:1
|
6 | struct DerefBool(bool);
| ^^^^^^^^^^^^^^^^
note: required by a bound in `anyhow::__private::not`
--> src/lib.rs
|
| pub fn not(cond: impl Bool) -> bool {
| ^^^^ required by this bound in `not`
help: consider dereferencing here
|
33 | ensure!(*db);
| +
error[E0277]: the trait bound `&DerefBool: anyhow::__private::not::Bool` is not satisfied
--> tests/ui/ensure-nonbool.rs:34:13
|
34 | ensure!(&db);
| --------^^^-
| | |
| | the trait `anyhow::__private::not::Bool` is not implemented for `&DerefBool`
| required by a bound introduced by this call
|
note: required by a bound in `anyhow::__private::not`
--> src/lib.rs
|
| pub fn not(cond: impl Bool) -> bool {
| ^^^^ required by this bound in `not`
help: consider dereferencing here
|
34 | ensure!(&*db);
| +
error[E0277]: the trait bound `NotBool: anyhow::__private::not::Bool` is not satisfied
--> tests/ui/ensure-nonbool.rs:37:13
|
37 | ensure!(nb);
| --------^^-
| | |
| | unsatisfied trait bound
| required by a bound introduced by this call
|
help: the trait `anyhow::__private::not::Bool` is not implemented for `NotBool`
--> tests/ui/ensure-nonbool.rs:8:1
|
8 | struct NotBool(bool);
| ^^^^^^^^^^^^^^
help: the following other types implement trait `anyhow::__private::not::Bool`
--> src/lib.rs
|
| impl Bool for bool {
| ^^^^^^^^^^^^^^^^^^ `bool`
...
| impl Bool for &bool {
| ^^^^^^^^^^^^^^^^^^^ `&bool`
note: required by a bound in `anyhow::__private::not`
--> src/lib.rs
|
| pub fn not(cond: impl Bool) -> bool {
| ^^^^ required by this bound in `not`
+11
View File
@@ -0,0 +1,11 @@
#![deny(unused_must_use)]
use anyhow::anyhow;
fn main() -> anyhow::Result<()> {
if true {
// meant to write bail!
anyhow!("it failed");
}
Ok(())
}
+15
View File
@@ -0,0 +1,15 @@
error: unused return value of `anyhow::__private::must_use` that must be used
--> tests/ui/must-use.rs:8:9
|
8 | anyhow!("it failed");
| ^^^^^^^^^^^^^^^^^^^^
|
note: the lint level is defined here
--> tests/ui/must-use.rs:1:9
|
1 | #![deny(unused_must_use)]
| ^^^^^^^^^^^^^^^
help: use `let _ = ...` to ignore the resulting value
|
8 | let _ = anyhow!("it failed");
| +++++++
+8
View File
@@ -0,0 +1,8 @@
use anyhow::anyhow;
#[derive(Debug)]
struct Error;
fn main() {
let _ = anyhow!(Error);
}
+32
View File
@@ -0,0 +1,32 @@
error[E0599]: the method `anyhow_kind` exists for reference `&Error`, but its trait bounds were not satisfied
--> tests/ui/no-impl.rs:7:13
|
4 | struct Error;
| ------------ doesn't satisfy `Error: Into<anyhow::Error>`, `Error: anyhow::kind::TraitKind` or `Error: std::fmt::Display`
...
7 | let _ = anyhow!(Error);
| ^^^^^^^^^^^^^^ method cannot be called on `&Error` due to unsatisfied trait bounds
|
= note: the following trait bounds were not satisfied:
`Error: Into<anyhow::Error>`
which is required by `Error: anyhow::kind::TraitKind`
`Error: std::fmt::Display`
which is required by `&Error: anyhow::kind::AdhocKind`
`&Error: Into<anyhow::Error>`
which is required by `&Error: anyhow::kind::TraitKind`
note: the traits `Into` and `std::fmt::Display` must be implemented
--> $RUST/core/src/fmt/mod.rs
|
| pub trait Display: PointeeSized {
| ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
|
::: $RUST/core/src/convert/mod.rs
|
| pub const trait Into<T>: Sized {
| ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
= help: items from traits can only be used if the trait is implemented and in scope
= note: the following traits define an item `anyhow_kind`, perhaps you need to implement one of them:
candidate #1: `anyhow::kind::AdhocKind`
candidate #2: `anyhow::kind::BoxedKind`
candidate #3: `anyhow::kind::TraitKind`
= note: this error originates in the macro `anyhow` (in Nightly builds, run with -Z macro-backtrace for more info)
+5
View File
@@ -0,0 +1,5 @@
use anyhow::anyhow;
fn main() {
let _ = anyhow!(&String::new());
}
+15
View File
@@ -0,0 +1,15 @@
error[E0716]: temporary value dropped while borrowed
--> tests/ui/temporary-value.rs:4:22
|
4 | let _ = anyhow!(&String::new());
| ---------^^^^^^^^^^^^^-
| | |
| | creates a temporary value which is freed while still in use
| temporary value is freed at the end of this statement
| argument requires that borrow lasts for `'static`
|
note: requirement that the value outlives `'static` introduced here
--> src/kind.rs
|
| M: Display + Debug + Send + Sync + 'static,
| ^^^^^^^
+5
View File
@@ -0,0 +1,5 @@
use anyhow::{bail, Result};
fn main() -> Result<()> {
bail!("{} not found");
}
+5
View File
@@ -0,0 +1,5 @@
error: 1 positional argument in format string, but no arguments were given
--> tests/ui/wrong-interpolation.rs:4:12
|
4 | bail!("{} not found");
| ^^
+1
View File
@@ -0,0 +1 @@
{"files":{".cargo_vcs_info.json":"bbdbd90be8e01b10f5516e44a9d4a09efc7e0c489eed3bb4e64adcde3b9d91a3","Cargo.lock":"152ebf71feed214951186ccc738958236b493bacf634d48ce422371c92ef0b07","Cargo.toml":"919934158a57ad16895ec2e937c5c14e3cef405b168c191d64dc81b69b9abe4a","Cargo.toml.orig":"958306c591cb50cd4b090194971e2eb213c641b304af82b2b36c31b60ffdc126","LICENSE-APACHE":"a60eea817514531668d7e00765731449fe14d059d3249e0bc93b36de45f759f2","LICENSE-MIT":"27995d58ad5c1145c1a8cd86244ce844886958a35eb2b78c6b772748669999ac","README.md":"fbd8762dcc681074fb3862e5d2168299fedb08d08bcadbff2713e2f956a79f82","examples/integers.rs":"589ff4271566dfa322becddf3e2c7b592e6e0bc97b02892ce75619b7e452e930","examples/nightly.rs":"ac8b5a9aa1e04465e44f5053b3c899b635e07af058c73aa8b45176bf4b5912f9","examples/paths.rs":"1b30e466b824ce8df7ad0a55334424131d9d2573d6cf9f7d5d50c09c8901d526","examples/traits.rs":"cbee6a3e1f7db60b02ae25b714926517144a77cb492021f492774cf0e1865a9e","examples/versions.rs":"38535e6d9f5bfae0de474a3db79a40e8f5da8ba9334c5ff4c363de9bc99d4d12","src/error.rs":"fd8ff67c64f7cd1b9f81325a81de4baa34c39d6ae298bdb33f9829cc91acac39","src/lib.rs":"772630d0e09d06f343fd72284c8330eacde87d2a4ee22f70af62a0e2119fbf05","src/rustc.rs":"a8a213ddb64a05c1a1af933bcb331a98879e942b167c33d8f94f9f60ebb14e29","src/tests.rs":"b39f4d880ad343e65307a9e0c381954ea27adce4732f825516ce7952e2e5a91d","src/version.rs":"4f7d23b36f01c7be1871be86c038d6cb4689e145d67c82d3793690e9aa05b133","tests/no_std.rs":"18859dc4992fe1769887bde05f03d28f1ce524eafd17646d3fbcb4379422761a","tests/rustflags.rs":"e8ded4d57ba25379a38ab48456d67df14f82abbbb5f6bb66221c6decbcb517a3","tests/support/mod.rs":"32087d365b438ac3f62df9bb066d8d648b80cb130a5c777afcb2f21fbb68d88e","tests/tests.rs":"39149fe69a45f577eeb531587a5e600d8bc9f8d937480172dac9402939d856f0","tests/wrap_ignored":"a9e241edf584a0702066b25bc15c5bbfd8a1019e14fb655fc4f47a67360065ca","tests/wrappers.rs":"b5137fe36768eaef277fb8650b0fe45b2c675d9e60b1b49d39f2ec95082c285b"},"package":"c08606f8c3cbf4ce6ec8e28fb0014a2c086708fe954eaa885384a6165172e7e8"}
+6
View File
@@ -0,0 +1,6 @@
{
"git": {
"sha1": "d912169ed67977efe5a465269b0e73cb66060c49"
},
"path_in_vcs": ""
}
Generated Vendored
+6
View File
@@ -0,0 +1,6 @@
# This file is automatically @generated by Cargo.
# It is not intended for manual editing.
[[package]]
name = "autocfg"
version = "1.5.0"
+76
View File
@@ -0,0 +1,76 @@
# THIS FILE IS AUTOMATICALLY GENERATED BY CARGO
#
# When uploading crates to the registry Cargo will automatically
# "normalize" Cargo.toml files for maximal compatibility
# with all versions of Cargo and also rewrite `path` dependencies
# to registry (e.g., crates.io) dependencies.
#
# If you are reading this file be aware that the original Cargo.toml
# will likely look very different (and much more reasonable).
# See Cargo.toml.orig for the original contents.
[package]
rust-version = "1.0"
name = "autocfg"
version = "1.5.0"
authors = ["Josh Stone <cuviper@gmail.com>"]
build = false
exclude = ["/.github/**"]
autolib = false
autobins = false
autoexamples = false
autotests = false
autobenches = false
description = "Automatic cfg for Rust compiler features"
documentation = "https://docs.rs/autocfg/"
readme = "README.md"
keywords = [
"rustc",
"build",
"autoconf",
]
categories = ["development-tools::build-utils"]
license = "Apache-2.0 OR MIT"
repository = "https://github.com/cuviper/autocfg"
[lib]
name = "autocfg"
path = "src/lib.rs"
[[example]]
name = "integers"
path = "examples/integers.rs"
[[example]]
name = "nightly"
path = "examples/nightly.rs"
[[example]]
name = "paths"
path = "examples/paths.rs"
[[example]]
name = "traits"
path = "examples/traits.rs"
[[example]]
name = "versions"
path = "examples/versions.rs"
[[test]]
name = "no_std"
path = "tests/no_std.rs"
[[test]]
name = "rustflags"
path = "tests/rustflags.rs"
[[test]]
name = "tests"
path = "tests/tests.rs"
[[test]]
name = "wrappers"
path = "tests/wrappers.rs"
[dependencies]
+201
View File
@@ -0,0 +1,201 @@
Apache License
Version 2.0, January 2004
http://www.apache.org/licenses/
TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION
1. Definitions.
"License" shall mean the terms and conditions for use, reproduction,
and distribution as defined by Sections 1 through 9 of this document.
"Licensor" shall mean the copyright owner or entity authorized by
the copyright owner that is granting the License.
"Legal Entity" shall mean the union of the acting entity and all
other entities that control, are controlled by, or are under common
control with that entity. For the purposes of this definition,
"control" means (i) the power, direct or indirect, to cause the
direction or management of such entity, whether by contract or
otherwise, or (ii) ownership of fifty percent (50%) or more of the
outstanding shares, or (iii) beneficial ownership of such entity.
"You" (or "Your") shall mean an individual or Legal Entity
exercising permissions granted by this License.
"Source" form shall mean the preferred form for making modifications,
including but not limited to software source code, documentation
source, and configuration files.
"Object" form shall mean any form resulting from mechanical
transformation or translation of a Source form, including but
not limited to compiled object code, generated documentation,
and conversions to other media types.
"Work" shall mean the work of authorship, whether in Source or
Object form, made available under the License, as indicated by a
copyright notice that is included in or attached to the work
(an example is provided in the Appendix below).
"Derivative Works" shall mean any work, whether in Source or Object
form, that is based on (or derived from) the Work and for which the
editorial revisions, annotations, elaborations, or other modifications
represent, as a whole, an original work of authorship. For the purposes
of this License, Derivative Works shall not include works that remain
separable from, or merely link (or bind by name) to the interfaces of,
the Work and Derivative Works thereof.
"Contribution" shall mean any work of authorship, including
the original version of the Work and any modifications or additions
to that Work or Derivative Works thereof, that is intentionally
submitted to Licensor for inclusion in the Work by the copyright owner
or by an individual or Legal Entity authorized to submit on behalf of
the copyright owner. For the purposes of this definition, "submitted"
means any form of electronic, verbal, or written communication sent
to the Licensor or its representatives, including but not limited to
communication on electronic mailing lists, source code control systems,
and issue tracking systems that are managed by, or on behalf of, the
Licensor for the purpose of discussing and improving the Work, but
excluding communication that is conspicuously marked or otherwise
designated in writing by the copyright owner as "Not a Contribution."
"Contributor" shall mean Licensor and any individual or Legal Entity
on behalf of whom a Contribution has been received by Licensor and
subsequently incorporated within the Work.
2. Grant of Copyright License. Subject to the terms and conditions of
this License, each Contributor hereby grants to You a perpetual,
worldwide, non-exclusive, no-charge, royalty-free, irrevocable
copyright license to reproduce, prepare Derivative Works of,
publicly display, publicly perform, sublicense, and distribute the
Work and such Derivative Works in Source or Object form.
3. Grant of Patent License. Subject to the terms and conditions of
this License, each Contributor hereby grants to You a perpetual,
worldwide, non-exclusive, no-charge, royalty-free, irrevocable
(except as stated in this section) patent license to make, have made,
use, offer to sell, sell, import, and otherwise transfer the Work,
where such license applies only to those patent claims licensable
by such Contributor that are necessarily infringed by their
Contribution(s) alone or by combination of their Contribution(s)
with the Work to which such Contribution(s) was submitted. If You
institute patent litigation against any entity (including a
cross-claim or counterclaim in a lawsuit) alleging that the Work
or a Contribution incorporated within the Work constitutes direct
or contributory patent infringement, then any patent licenses
granted to You under this License for that Work shall terminate
as of the date such litigation is filed.
4. Redistribution. You may reproduce and distribute copies of the
Work or Derivative Works thereof in any medium, with or without
modifications, and in Source or Object form, provided that You
meet the following conditions:
(a) You must give any other recipients of the Work or
Derivative Works a copy of this License; and
(b) You must cause any modified files to carry prominent notices
stating that You changed the files; and
(c) You must retain, in the Source form of any Derivative Works
that You distribute, all copyright, patent, trademark, and
attribution notices from the Source form of the Work,
excluding those notices that do not pertain to any part of
the Derivative Works; and
(d) If the Work includes a "NOTICE" text file as part of its
distribution, then any Derivative Works that You distribute must
include a readable copy of the attribution notices contained
within such NOTICE file, excluding those notices that do not
pertain to any part of the Derivative Works, in at least one
of the following places: within a NOTICE text file distributed
as part of the Derivative Works; within the Source form or
documentation, if provided along with the Derivative Works; or,
within a display generated by the Derivative Works, if and
wherever such third-party notices normally appear. The contents
of the NOTICE file are for informational purposes only and
do not modify the License. You may add Your own attribution
notices within Derivative Works that You distribute, alongside
or as an addendum to the NOTICE text from the Work, provided
that such additional attribution notices cannot be construed
as modifying the License.
You may add Your own copyright statement to Your modifications and
may provide additional or different license terms and conditions
for use, reproduction, or distribution of Your modifications, or
for any such Derivative Works as a whole, provided Your use,
reproduction, and distribution of the Work otherwise complies with
the conditions stated in this License.
5. Submission of Contributions. Unless You explicitly state otherwise,
any Contribution intentionally submitted for inclusion in the Work
by You to the Licensor shall be under the terms and conditions of
this License, without any additional terms or conditions.
Notwithstanding the above, nothing herein shall supersede or modify
the terms of any separate license agreement you may have executed
with Licensor regarding such Contributions.
6. Trademarks. This License does not grant permission to use the trade
names, trademarks, service marks, or product names of the Licensor,
except as required for reasonable and customary use in describing the
origin of the Work and reproducing the content of the NOTICE file.
7. Disclaimer of Warranty. Unless required by applicable law or
agreed to in writing, Licensor provides the Work (and each
Contributor provides its Contributions) on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or
implied, including, without limitation, any warranties or conditions
of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A
PARTICULAR PURPOSE. You are solely responsible for determining the
appropriateness of using or redistributing the Work and assume any
risks associated with Your exercise of permissions under this License.
8. Limitation of Liability. In no event and under no legal theory,
whether in tort (including negligence), contract, or otherwise,
unless required by applicable law (such as deliberate and grossly
negligent acts) or agreed to in writing, shall any Contributor be
liable to You for damages, including any direct, indirect, special,
incidental, or consequential damages of any character arising as a
result of this License or out of the use or inability to use the
Work (including but not limited to damages for loss of goodwill,
work stoppage, computer failure or malfunction, or any and all
other commercial damages or losses), even if such Contributor
has been advised of the possibility of such damages.
9. Accepting Warranty or Additional Liability. While redistributing
the Work or Derivative Works thereof, You may choose to offer,
and charge a fee for, acceptance of support, warranty, indemnity,
or other liability obligations and/or rights consistent with this
License. However, in accepting such obligations, You may act only
on Your own behalf and on Your sole responsibility, not on behalf
of any other Contributor, and only if You agree to indemnify,
defend, and hold each Contributor harmless for any liability
incurred by, or claims asserted against, such Contributor by reason
of your accepting any such warranty or additional liability.
END OF TERMS AND CONDITIONS
APPENDIX: How to apply the Apache License to your work.
To apply the Apache License to your work, attach the following
boilerplate notice, with the fields enclosed by brackets "[]"
replaced with your own identifying information. (Don't include
the brackets!) The text should be enclosed in the appropriate
comment syntax for the file format. We also recommend that a
file or class name and description of purpose be included on the
same "printed page" as the copyright notice for easier
identification within third-party archives.
Copyright [yyyy] [name of copyright owner]
Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.
You may obtain a copy of the License at
http://www.apache.org/licenses/LICENSE-2.0
Unless required by applicable law or agreed to in writing, software
distributed under the License is distributed on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the License for the specific language governing permissions and
limitations under the License.
+25
View File
@@ -0,0 +1,25 @@
Copyright (c) 2018 Josh Stone
Permission is hereby granted, free of charge, to any
person obtaining a copy of this software and associated
documentation files (the "Software"), to deal in the
Software without restriction, including without
limitation the rights to use, copy, modify, merge,
publish, distribute, sublicense, and/or sell copies of
the Software, and to permit persons to whom the Software
is furnished to do so, subject to the following
conditions:
The above copyright notice and this permission notice
shall be included in all copies or substantial portions
of the Software.
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF
ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED
TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A
PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT
SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY
CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR
IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER
DEALINGS IN THE SOFTWARE.
+117
View File
@@ -0,0 +1,117 @@
autocfg
=======
[![autocfg crate](https://img.shields.io/crates/v/autocfg.svg)](https://crates.io/crates/autocfg)
[![autocfg documentation](https://docs.rs/autocfg/badge.svg)](https://docs.rs/autocfg)
![minimum rustc 1.0](https://img.shields.io/badge/rustc-1.0+-red.svg)
![build status](https://github.com/cuviper/autocfg/workflows/CI/badge.svg)
A Rust library for build scripts to automatically configure code based on
compiler support. Code snippets are dynamically tested to see if the `rustc`
will accept them, rather than hard-coding specific version support.
## Usage
Add this to your `Cargo.toml`:
```toml
[build-dependencies]
autocfg = "1"
```
Then use it in your `build.rs` script to detect compiler features. For
example, to test for 128-bit integer support, it might look like:
```rust
extern crate autocfg;
fn main() {
let ac = autocfg::new();
ac.emit_has_type("i128");
// (optional) We don't need to rerun for anything external.
autocfg::rerun_path("build.rs");
}
```
If the type test succeeds, this will write a `cargo:rustc-cfg=has_i128` line
for Cargo, which translates to Rust arguments `--cfg has_i128`. Then in the
rest of your Rust code, you can add `#[cfg(has_i128)]` conditions on code that
should only be used when the compiler supports it.
## Release Notes
- 1.5.0 (2025-06-17)
- Add `edition` and `set_edition` to control the Rust edition used in probes.
- Remove probe result files so they don't pollute the output directory.
- 1.4.0 (2024-09-26)
- Add `emit_possibility` for Rust 1.80's [checked cfgs], and call that
automatically for methods that conditionally `emit`, by @Techcable.
[checked cfgs]: https://blog.rust-lang.org/2024/05/06/check-cfg.html
- 1.3.0 (2024-05-03)
- Add `probe_raw` for direct control of the code that will be test-compiled.
- Use wrappers when querying the `rustc` version information too.
- 1.2.0 (2024-03-25)
- Add `no_std` and `set_no_std` to control the use of `#![no_std]` in probes.
- Use `RUSTC_WRAPPER` and `RUSTC_WORKSPACE_WRAPPER` when they are set.
- 1.1.0 (2022-02-07)
- Use `CARGO_ENCODED_RUSTFLAGS` when it is set.
- 1.0.1 (2020-08-20)
- Apply `RUSTFLAGS` for more `--target` scenarios, by @adamreichold.
- 1.0.0 (2020-01-08)
- 🎉 Release 1.0! 🎉 (no breaking changes)
- Add `probe_expression` and `emit_expression_cfg` to test arbitrary expressions.
- Add `probe_constant` and `emit_constant_cfg` to test arbitrary constant expressions.
- 0.1.7 (2019-10-20)
- Apply `RUSTFLAGS` when probing `$TARGET != $HOST`, mainly for sysroot, by @roblabla.
- 0.1.6 (2019-08-19)
- Add `probe`/`emit_sysroot_crate`, by @leo60228.
- 0.1.5 (2019-07-16)
- Mask some warnings from newer rustc.
- 0.1.4 (2019-05-22)
- Relax `std`/`no_std` probing to a warning instead of an error.
- Improve `rustc` bootstrap compatibility.
- 0.1.3 (2019-05-21)
- Auto-detects if `#![no_std]` is needed for the `$TARGET`.
- 0.1.2 (2019-01-16)
- Add `rerun_env(ENV)` to print `cargo:rerun-if-env-changed=ENV`.
- Add `rerun_path(PATH)` to print `cargo:rerun-if-changed=PATH`.
## Minimum Rust version policy
This crate's minimum supported `rustc` version is `1.0.0`. Compatibility is
its entire reason for existence, so this crate will be extremely conservative
about raising this requirement. If this is ever deemed necessary, it will be
treated as a major breaking change for semver purposes.
## License
This project is licensed under either of
* Apache License, Version 2.0, ([LICENSE-APACHE](LICENSE-APACHE) or
https://www.apache.org/licenses/LICENSE-2.0)
* MIT license ([LICENSE-MIT](LICENSE-MIT) or
https://opensource.org/licenses/MIT)
at your option.
+9
View File
@@ -0,0 +1,9 @@
extern crate autocfg;
fn main() {
// Normally, cargo will set `OUT_DIR` for build scripts.
let ac = autocfg::AutoCfg::with_dir("target").unwrap();
for i in 3..8 {
ac.emit_has_type(&format!("i{}", 1 << i));
}
}
+18
View File
@@ -0,0 +1,18 @@
extern crate autocfg;
fn main() {
// Normally, cargo will set `OUT_DIR` for build scripts.
let ac = autocfg::AutoCfg::with_dir("target").unwrap();
// When this feature was stabilized, it also renamed the method to
// `chunk_by`, so it's important to *use* the feature in your probe.
let code = r#"
#![feature(slice_group_by)]
pub fn probe(slice: &[i32]) -> impl Iterator<Item = &[i32]> {
slice.group_by(|a, b| a == b)
}
"#;
if ac.probe_raw(code).is_ok() {
autocfg::emit("has_slice_group_by");
}
}
+22
View File
@@ -0,0 +1,22 @@
extern crate autocfg;
fn main() {
// Normally, cargo will set `OUT_DIR` for build scripts.
let ac = autocfg::AutoCfg::with_dir("target").unwrap();
// since ancient times...
ac.emit_has_path("std::vec::Vec");
ac.emit_path_cfg("std::vec::Vec", "has_vec");
// rustc 1.10.0
ac.emit_has_path("std::panic::PanicInfo");
ac.emit_path_cfg("std::panic::PanicInfo", "has_panic_info");
// rustc 1.20.0
ac.emit_has_path("std::mem::ManuallyDrop");
ac.emit_path_cfg("std::mem::ManuallyDrop", "has_manually_drop");
// rustc 1.25.0
ac.emit_has_path("std::ptr::NonNull");
ac.emit_path_cfg("std::ptr::NonNull", "has_non_null");
}
+26
View File
@@ -0,0 +1,26 @@
extern crate autocfg;
fn main() {
// Normally, cargo will set `OUT_DIR` for build scripts.
let ac = autocfg::AutoCfg::with_dir("target").unwrap();
// since ancient times...
ac.emit_has_trait("std::ops::Add");
ac.emit_trait_cfg("std::ops::Add", "has_ops");
// trait parameters have to be provided
ac.emit_has_trait("std::borrow::Borrow<str>");
ac.emit_trait_cfg("std::borrow::Borrow<str>", "has_borrow");
// rustc 1.8.0
ac.emit_has_trait("std::ops::AddAssign");
ac.emit_trait_cfg("std::ops::AddAssign", "has_assign_ops");
// rustc 1.12.0
ac.emit_has_trait("std::iter::Sum");
ac.emit_trait_cfg("std::iter::Sum", "has_sum");
// rustc 1.28.0
ac.emit_has_trait("std::alloc::GlobalAlloc");
ac.emit_trait_cfg("std::alloc::GlobalAlloc", "has_global_alloc");
}
+9
View File
@@ -0,0 +1,9 @@
extern crate autocfg;
fn main() {
// Normally, cargo will set `OUT_DIR` for build scripts.
let ac = autocfg::AutoCfg::with_dir("target").unwrap();
for i in 0..100 {
ac.emit_rustc_version(1, i);
}
}
+81
View File
@@ -0,0 +1,81 @@
use std::error;
use std::fmt;
use std::io;
use std::num;
use std::process;
use std::str;
/// A common error type for the `autocfg` crate.
#[derive(Debug)]
pub struct Error {
kind: ErrorKind,
}
impl error::Error for Error {
fn description(&self) -> &str {
"AutoCfg error"
}
fn cause(&self) -> Option<&error::Error> {
match self.kind {
ErrorKind::Io(ref e) => Some(e),
ErrorKind::Num(ref e) => Some(e),
ErrorKind::Utf8(ref e) => Some(e),
ErrorKind::Process(_) | ErrorKind::Other(_) => None,
}
}
}
impl fmt::Display for Error {
fn fmt(&self, f: &mut fmt::Formatter) -> Result<(), fmt::Error> {
match self.kind {
ErrorKind::Io(ref e) => e.fmt(f),
ErrorKind::Num(ref e) => e.fmt(f),
ErrorKind::Utf8(ref e) => e.fmt(f),
ErrorKind::Process(ref status) => {
// Same message as the newer `ExitStatusError`
write!(f, "process exited unsuccessfully: {}", status)
}
ErrorKind::Other(s) => s.fmt(f),
}
}
}
#[derive(Debug)]
enum ErrorKind {
Io(io::Error),
Num(num::ParseIntError),
Process(process::ExitStatus),
Utf8(str::Utf8Error),
Other(&'static str),
}
pub fn from_exit(status: process::ExitStatus) -> Error {
Error {
kind: ErrorKind::Process(status),
}
}
pub fn from_io(e: io::Error) -> Error {
Error {
kind: ErrorKind::Io(e),
}
}
pub fn from_num(e: num::ParseIntError) -> Error {
Error {
kind: ErrorKind::Num(e),
}
}
pub fn from_utf8(e: str::Utf8Error) -> Error {
Error {
kind: ErrorKind::Utf8(e),
}
}
pub fn from_str(s: &'static str) -> Error {
Error {
kind: ErrorKind::Other(s),
}
}
Loaded 100 of 17624 files, more files were not shown because too many files have changed in this diff. Show more