nushell/crates/nu-command/src/path/mod.rs
Stefan Holderbach a52386e837
Box ShellError in Value::Error (#8375)
# Description

Our `ShellError` at the moment has a `std::mem::size_of<ShellError>` of
136 bytes (on AMD64). As a result `Value` directly storing the struct
also required 136 bytes (thanks to alignment requirements).

This change stores the `Value::Error` `ShellError` on the heap.

Pro:
- Value now needs just 80 bytes
- Should be 1 cacheline less (still at least 2 cachelines)

Con:
- More small heap allocations when dealing with `Value::Error`
  - More heap fragmentation
  - Potential for additional required memcopies

# Further code changes

Includes a small refactor of `try` due to a type mismatch in its large
match.

# User-Facing Changes

None for regular users.

Plugin authors may have to update their matches on `Value` if they use
`nu-protocol`

Needs benchmarking to see if there is a benefit in real world workloads.
**Update** small improvements in runtime for workloads with high volume
of values. Significant reduction in maximum resident set size, when many
values are held in memory.

# Tests + Formatting
2023-03-12 09:57:27 +01:00

113 lines
3.3 KiB
Rust

mod basename;
mod dirname;
mod exists;
mod expand;
mod join;
mod parse;
pub mod path_;
mod relative_to;
mod split;
mod r#type;
use std::path::Path as StdPath;
pub use basename::SubCommand as PathBasename;
pub use dirname::SubCommand as PathDirname;
pub use exists::SubCommand as PathExists;
pub use expand::SubCommand as PathExpand;
pub use join::SubCommand as PathJoin;
pub use parse::SubCommand as PathParse;
pub use path_::PathCommand as Path;
pub use r#type::SubCommand as PathType;
pub use relative_to::SubCommand as PathRelativeTo;
pub use split::SubCommand as PathSplit;
use nu_protocol::{ShellError, Span, Value};
#[cfg(windows)]
const ALLOWED_COLUMNS: [&str; 4] = ["prefix", "parent", "stem", "extension"];
#[cfg(not(windows))]
const ALLOWED_COLUMNS: [&str; 3] = ["parent", "stem", "extension"];
trait PathSubcommandArguments {
fn get_columns(&self) -> Option<Vec<String>>;
}
fn operate<F, A>(cmd: &F, args: &A, v: Value, name: Span) -> Value
where
F: Fn(&StdPath, Span, &A) -> Value + Send + Sync + 'static,
A: PathSubcommandArguments + Send + Sync + 'static,
{
match v {
Value::String { val, span } => cmd(StdPath::new(&val), span, args),
Value::Record { cols, vals, span } => {
let col = if let Some(col) = args.get_columns() {
col
} else {
vec![]
};
if col.is_empty() {
return Value::Error {
error: Box::new(ShellError::UnsupportedInput(
String::from("when the input is a table, you must specify the columns"),
"value originates from here".into(),
name,
span,
)),
};
}
let mut output_cols = vec![];
let mut output_vals = vec![];
for (k, v) in cols.iter().zip(vals) {
output_cols.push(k.clone());
if col.contains(k) {
let new_val = match v {
Value::String { val, span } => cmd(StdPath::new(&val), span, args),
_ => return handle_invalid_values(v, name),
};
output_vals.push(new_val);
} else {
output_vals.push(v);
}
}
Value::Record {
cols: output_cols,
vals: output_vals,
span,
}
}
_ => handle_invalid_values(v, name),
}
}
fn handle_invalid_values(rest: Value, name: Span) -> Value {
Value::Error {
error: Box::new(err_from_value(&rest, name)),
}
}
fn err_from_value(rest: &Value, name: Span) -> ShellError {
match rest.span() {
Ok(span) => {
if rest.is_nothing() {
ShellError::OnlySupportsThisInputType {
exp_input_type: "string, record or list".into(),
wrong_type: "nothing".into(),
dst_span: name,
src_span: span,
}
} else {
ShellError::PipelineMismatch {
exp_input_type: "string, row or list".into(),
dst_span: name,
src_span: span,
}
}
}
Err(error) => error,
}
}