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:
17624 files changed
+4564928
-38
No files matched your search
@@ -1,2 +1,3 @@
|
||||
/target
|
||||
/vendor
|
||||
src/board/mod.rs
|
||||
Generated
+149
-4
@@ -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
@@ -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"] }
|
||||
|
||||
@@ -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"
|
||||
|
||||
|
||||
@@ -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
@@ -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
@@ -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
@@ -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();
|
||||
}
|
||||
}
|
||||
@@ -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,
|
||||
}
|
||||
@@ -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 {
|
||||
|
||||
+24
-3
@@ -1,23 +1,44 @@
|
||||
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()
|
||||
// resp = responses.receive() => serialize Response → cdc_write()
|
||||
// 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 {
|
||||
|
||||
Vendored
+1
@@ -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"}
|
||||
Vendored
+6
@@ -0,0 +1,6 @@
|
||||
{
|
||||
"git": {
|
||||
"sha1": "9aa1ba20f05ed582eda04ea625d5658c92195a57"
|
||||
},
|
||||
"path_in_vcs": ""
|
||||
}
|
||||
+156
@@ -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
|
||||
Vendored
+183
@@ -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
|
||||
Vendored
+118
@@ -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.
|
||||
Vendored
+201
@@ -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.
|
||||
Vendored
+25
@@ -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.
|
||||
Vendored
+109
@@ -0,0 +1,109 @@
|
||||
# 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)
|
||||
|
||||

|
||||
|
||||
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.
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
Vendored
+22
@@ -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");
|
||||
}
|
||||
}
|
||||
Vendored
+1
@@ -0,0 +1 @@
|
||||
max_width = 120
|
||||
Vendored
+426
@@ -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));
|
||||
}
|
||||
}
|
||||
Vendored
+151
@@ -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()
|
||||
}
|
||||
}
|
||||
Vendored
+360
@@ -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));
|
||||
}
|
||||
}
|
||||
Vendored
+501
@@ -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
@@ -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));
|
||||
}
|
||||
}
|
||||
Vendored
+352
@@ -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);
|
||||
}
|
||||
}
|
||||
Vendored
+358
@@ -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);
|
||||
}
|
||||
}
|
||||
Vendored
+374
@@ -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);
|
||||
}
|
||||
}
|
||||
Vendored
+523
@@ -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);
|
||||
}
|
||||
}
|
||||
Vendored
+214
@@ -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)
|
||||
);
|
||||
}
|
||||
}
|
||||
Vendored
+199
@@ -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
|
||||
);
|
||||
Vendored
+310
@@ -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,);
|
||||
Vendored
+81
@@ -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");
|
||||
}
|
||||
Vendored
+1
@@ -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"}
|
||||
Vendored
+6
@@ -0,0 +1,6 @@
|
||||
{
|
||||
"git": {
|
||||
"sha1": "5c657b32522023a9f7ef883fb08582fd8e656b1a"
|
||||
},
|
||||
"path_in_vcs": ""
|
||||
}
|
||||
Vendored
+1
@@ -0,0 +1 @@
|
||||
github: dtolnay
|
||||
+146
@@ -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
|
||||
+344
@@ -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"
|
||||
Vendored
+129
@@ -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"]
|
||||
Vendored
+176
@@ -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
|
||||
Vendored
+23
@@ -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.
|
||||
Vendored
+179
@@ -0,0 +1,179 @@
|
||||
Anyhow ¯\\\_(°ペ)\_/¯
|
||||
==========================
|
||||
|
||||
[<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 ≥ 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>
|
||||
Vendored
+194
@@ -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);
|
||||
})
|
||||
}
|
||||
Vendored
+2
@@ -0,0 +1,2 @@
|
||||
[toolchain]
|
||||
components = ["rust-src"]
|
||||
Vendored
+48
@@ -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>();
|
||||
}
|
||||
Vendored
+102
@@ -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(),
|
||||
},
|
||||
}
|
||||
}
|
||||
}
|
||||
Vendored
+193
@@ -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> {}
|
||||
}
|
||||
Vendored
+935
@@ -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)*));
|
||||
}
|
||||
};
|
||||
}
|
||||
Vendored
+1059
File diff suppressed because it is too large.
Load diff
Vendored
+158
@@ -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);
|
||||
}
|
||||
}
|
||||
Vendored
+121
@@ -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)
|
||||
}
|
||||
}
|
||||
Vendored
+728
@@ -0,0 +1,728 @@
|
||||
//! [![github]](https://github.com/dtolnay/anyhow) [![crates-io]](https://crates.io/crates/anyhow) [![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 ≥ 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 — 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 — 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
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
Vendored
+245
@@ -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<_, [anyhow::Error][crate::Error]></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<_, [anyhow::Error][crate::Error]></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)*))
|
||||
};
|
||||
}
|
||||
Vendored
+58
@@ -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);
|
||||
}
|
||||
Vendored
+187
@@ -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;
|
||||
}
|
||||
Vendored
+84
@@ -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);
|
||||
}
|
||||
}
|
||||
Vendored
+14
@@ -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!"));
|
||||
}
|
||||
Vendored
+7
@@ -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");
|
||||
}
|
||||
Vendored
+53
@@ -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
@@ -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
@@ -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();
|
||||
}
|
||||
Vendored
+45
@@ -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());
|
||||
}
|
||||
Vendored
+69
@@ -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());
|
||||
}
|
||||
Vendored
+172
@@ -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");
|
||||
}
|
||||
Vendored
+46
@@ -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(())
|
||||
}
|
||||
Vendored
+123
@@ -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());
|
||||
}
|
||||
Vendored
+756
@@ -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)",
|
||||
);
|
||||
}
|
||||
Vendored
+19
@@ -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"))
|
||||
}
|
||||
Vendored
+93
@@ -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()));
|
||||
}
|
||||
Vendored
+97
@@ -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());
|
||||
}
|
||||
Vendored
+30
@@ -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());
|
||||
}
|
||||
Vendored
+62
@@ -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());
|
||||
}
|
||||
@@ -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(())
|
||||
}
|
||||
@@ -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
@@ -0,0 +1,6 @@
|
||||
use anyhow::{ensure, Result};
|
||||
|
||||
fn main() -> Result<()> {
|
||||
ensure!();
|
||||
Ok(())
|
||||
}
|
||||
+12
@@ -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
@@ -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
@@ -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`
|
||||
Vendored
+11
@@ -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
@@ -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");
|
||||
| +++++++
|
||||
Vendored
+8
@@ -0,0 +1,8 @@
|
||||
use anyhow::anyhow;
|
||||
|
||||
#[derive(Debug)]
|
||||
struct Error;
|
||||
|
||||
fn main() {
|
||||
let _ = anyhow!(Error);
|
||||
}
|
||||
+32
@@ -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
@@ -0,0 +1,5 @@
|
||||
use anyhow::anyhow;
|
||||
|
||||
fn main() {
|
||||
let _ = anyhow!(&String::new());
|
||||
}
|
||||
+15
@@ -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,
|
||||
| ^^^^^^^
|
||||
@@ -0,0 +1,5 @@
|
||||
use anyhow::{bail, Result};
|
||||
|
||||
fn main() -> Result<()> {
|
||||
bail!("{} not found");
|
||||
}
|
||||
@@ -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");
|
||||
| ^^
|
||||
Vendored
+1
@@ -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"}
|
||||
Vendored
+6
@@ -0,0 +1,6 @@
|
||||
{
|
||||
"git": {
|
||||
"sha1": "d912169ed67977efe5a465269b0e73cb66060c49"
|
||||
},
|
||||
"path_in_vcs": ""
|
||||
}
|
||||
+6
@@ -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"
|
||||
|
||||
Vendored
+76
@@ -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]
|
||||
Vendored
+201
@@ -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.
|
||||
Vendored
+25
@@ -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.
|
||||
Vendored
+117
@@ -0,0 +1,117 @@
|
||||
autocfg
|
||||
=======
|
||||
|
||||
[](https://crates.io/crates/autocfg)
|
||||
[](https://docs.rs/autocfg)
|
||||

|
||||

|
||||
|
||||
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.
|
||||
Vendored
+9
@@ -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));
|
||||
}
|
||||
}
|
||||
Vendored
+18
@@ -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");
|
||||
}
|
||||
}
|
||||
Vendored
+22
@@ -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");
|
||||
}
|
||||
Vendored
+26
@@ -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");
|
||||
}
|
||||
Vendored
+9
@@ -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);
|
||||
}
|
||||
}
|
||||
Vendored
+81
@@ -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
Reference in new issue
Block a user