Skip to main content

use_wasm_target/
lib.rs

1#![forbid(unsafe_code)]
2#![doc = include_str!("../README.md")]
3
4use core::{fmt, str::FromStr};
5use std::error::Error;
6
7/// Error returned when a Rust WebAssembly target triple cannot be parsed.
8#[derive(Clone, Copy, Debug, Eq, Hash, Ord, PartialEq, PartialOrd)]
9pub enum WasmTargetError {
10    /// The supplied target label was empty.
11    Empty,
12    /// The supplied target label is not in the known target list.
13    Unknown,
14}
15
16impl fmt::Display for WasmTargetError {
17    fn fmt(&self, formatter: &mut fmt::Formatter<'_>) -> fmt::Result {
18        match self {
19            Self::Empty => formatter.write_str("WebAssembly target cannot be empty"),
20            Self::Unknown => formatter.write_str("unknown WebAssembly target"),
21        }
22    }
23}
24
25impl Error for WasmTargetError {}
26
27/// ABI or profile family for a Rust WebAssembly target.
28#[derive(Clone, Copy, Debug, Default, Eq, Hash, Ord, PartialEq, PartialOrd)]
29pub enum WasmTargetProfile {
30    /// Bare Wasm with no host interface implied.
31    #[default]
32    UnknownUnknown,
33    /// WASI Preview 1.
34    WasiPreview1,
35    /// WASI Preview 1 with threads.
36    WasiPreview1Threads,
37    /// WASI Preview 2.
38    WasiPreview2,
39    /// Core Wasm 1.0 profile without std.
40    Wasm32V1None,
41}
42
43impl WasmTargetProfile {
44    /// Returns a stable profile label.
45    #[must_use]
46    pub const fn as_str(self) -> &'static str {
47        match self {
48            Self::UnknownUnknown => "unknown-unknown",
49            Self::WasiPreview1 => "wasip1",
50            Self::WasiPreview1Threads => "wasip1-threads",
51            Self::WasiPreview2 => "wasip2",
52            Self::Wasm32V1None => "wasm32v1-none",
53        }
54    }
55}
56
57impl fmt::Display for WasmTargetProfile {
58    fn fmt(&self, formatter: &mut fmt::Formatter<'_>) -> fmt::Result {
59        formatter.write_str(self.as_str())
60    }
61}
62
63/// Common Rust WebAssembly compilation targets.
64#[derive(Clone, Copy, Debug, Default, Eq, Hash, Ord, PartialEq, PartialOrd)]
65pub enum WasmTarget {
66    /// 'wasm32-unknown-unknown'.
67    #[default]
68    Wasm32UnknownUnknown,
69    /// 'wasm32-wasip1'.
70    Wasm32WasiP1,
71    /// 'wasm32-wasip1-threads'.
72    Wasm32WasiP1Threads,
73    /// 'wasm32-wasip2'.
74    Wasm32WasiP2,
75    /// 'wasm32v1-none'.
76    Wasm32V1None,
77}
78
79impl WasmTarget {
80    /// Returns the Rust target triple label.
81    #[must_use]
82    pub const fn as_str(self) -> &'static str {
83        match self {
84            Self::Wasm32UnknownUnknown => "wasm32-unknown-unknown",
85            Self::Wasm32WasiP1 => "wasm32-wasip1",
86            Self::Wasm32WasiP1Threads => "wasm32-wasip1-threads",
87            Self::Wasm32WasiP2 => "wasm32-wasip2",
88            Self::Wasm32V1None => "wasm32v1-none",
89        }
90    }
91
92    /// Returns the target family.
93    #[must_use]
94    pub const fn family(self) -> &'static str {
95        "wasm32"
96    }
97
98    /// Returns the pointer width in bits.
99    #[must_use]
100    pub const fn pointer_width(self) -> u8 {
101        32
102    }
103
104    /// Returns the ABI or profile label.
105    #[must_use]
106    pub const fn profile(self) -> WasmTargetProfile {
107        match self {
108            Self::Wasm32UnknownUnknown => WasmTargetProfile::UnknownUnknown,
109            Self::Wasm32WasiP1 => WasmTargetProfile::WasiPreview1,
110            Self::Wasm32WasiP1Threads => WasmTargetProfile::WasiPreview1Threads,
111            Self::Wasm32WasiP2 => WasmTargetProfile::WasiPreview2,
112            Self::Wasm32V1None => WasmTargetProfile::Wasm32V1None,
113        }
114    }
115
116    /// Returns 'true' when the target label explicitly includes thread support.
117    #[must_use]
118    pub const fn supports_threads(self) -> bool {
119        matches!(self, Self::Wasm32WasiP1Threads)
120    }
121}
122
123impl fmt::Display for WasmTarget {
124    fn fmt(&self, formatter: &mut fmt::Formatter<'_>) -> fmt::Result {
125        formatter.write_str(self.as_str())
126    }
127}
128
129impl FromStr for WasmTarget {
130    type Err = WasmTargetError;
131
132    fn from_str(value: &str) -> Result<Self, Self::Err> {
133        let trimmed = value.trim();
134        if trimmed.is_empty() {
135            return Err(WasmTargetError::Empty);
136        }
137        match trimmed.to_ascii_lowercase().as_str() {
138            "wasm32-unknown-unknown" => Ok(Self::Wasm32UnknownUnknown),
139            "wasm32-wasip1" => Ok(Self::Wasm32WasiP1),
140            "wasm32-wasip1-threads" => Ok(Self::Wasm32WasiP1Threads),
141            "wasm32-wasip2" => Ok(Self::Wasm32WasiP2),
142            "wasm32v1-none" => Ok(Self::Wasm32V1None),
143            _ => Err(WasmTargetError::Unknown),
144        }
145    }
146}
147
148#[cfg(test)]
149mod tests {
150    use super::{WasmTarget, WasmTargetError, WasmTargetProfile};
151
152    #[test]
153    fn parses_common_targets() {
154        assert_eq!(
155            "wasm32-wasip1".parse::<WasmTarget>(),
156            Ok(WasmTarget::Wasm32WasiP1)
157        );
158        assert_eq!("".parse::<WasmTarget>(), Err(WasmTargetError::Empty));
159    }
160
161    #[test]
162    fn exposes_target_metadata() {
163        let target = WasmTarget::Wasm32WasiP1Threads;
164
165        assert_eq!(target.family(), "wasm32");
166        assert_eq!(target.pointer_width(), 32);
167        assert_eq!(target.profile(), WasmTargetProfile::WasiPreview1Threads);
168        assert!(target.supports_threads());
169        assert_eq!(target.to_string(), "wasm32-wasip1-threads");
170    }
171}