Repository: nushell/nushell (language: rust)

Issue:
`str substring ..0` panic if input is an empty string
### Describe the bug

Hi everyone !

Found this bug 
```sh
> '' | str substring ..0
Error:
  × Main thread panicked.
  ├─▶ at crates/nu-command/src/strings/str_/substring.rs:165:58
  ╰─▶ range end index 1 out of range for slice of length 0
  help: set the `RUST_BACKTRACE=1` environment variable to display a backtrace.
```

No panic with `'' | str substring ..<0`.

Tried with nushell `0.102.0` and `0.102.1-nightly.4`.

### How to reproduce

1. Open a nushell interactive session
2. Run `'' | str substring ..0`

### Expected behavior

I expect an empty string as result.

### Configuration

| key                | value                                       |
| ------------------ | ------------------------------------------- |
| version            | 0.102.0                                     |
| major              | 0                                           |
| minor              | 102                                         |
| patch              | 0                                           |
| branch             |                                             |
| commit_hash        | 1aa2ed1947a0b891398558fcf4e4289849cc5a1d    |
| build_os           | linux-x86_64                                |
| build_target       | x86_64-unknown-linux-musl                   |
| rust_version       | rustc 1.82.0 (f6e511eec 2024-10-15)         |
| rust_channel       | 1.82.0-x86_64-unknown-linux-gnu             |
| cargo_version      | cargo 1.82.0 (8f40fc59f 2024-08-21)         |
| build_time         | 2025-02-05 00:56:06 +00:00                  |
| build_rust_channel | release                                     |
| allocator          | mimalloc                                    |
| features           | default, sqlite, static-link-openssl, trash |
| installed_plugins  |                                             |


Discussion / hints:
Heads up, the error is a different one depending on release mode or debug mode:

```
nushell on  main via 🦀 v1.82.0 took 48s 
101 nu ❯ ./target/debug/nu.exe -c "'' | str substring ..0"
Error:   × Main thread panicked.
  ├─▶ at crates\nu-protocol\src\value\range.rs:116:42
  ╰─▶ attempt to subtract with overflow
  help: set the `RUST_BACKTRACE=1` environment variable to display a backtrace.


nushell on  main via 🦀 v1.82.0 
101 nu ❯ ./target/release/nu.exe -c "'' | str substring ..0"
Error:   × Main thread panicked.
  ├─▶ at crates\nu-command\src\strings\str_\substring.rs:165:58
  ╰─▶ range end index 1 out of range for slice of length 0
  help: set the `RUST_BACKTRACE=1` environment variable to display a backtrace.
```

Fix patch:
diff --git a/crates/nu-protocol/src/value/range.rs b/crates/nu-protocol/src/value/range.rs
index 9ce9c6fa54202..5524a79fe3cca 100644
--- a/crates/nu-protocol/src/value/range.rs
+++ b/crates/nu-protocol/src/value/range.rs
@@ -112,8 +112,9 @@ mod int_range {
             match self.end {
                 Bound::Unbounded => Bound::Unbounded,
                 Bound::Included(i) => match i {
+                    _ if len == 0 => Bound::Excluded(0),
                     i if i < 0 => Bound::Excluded(len.saturating_sub((i + 1).unsigned_abs())),
-                    i => Bound::Included((len - 1).min(i.unsigned_abs())),
+                    i => Bound::Included((len.saturating_sub(1)).min(i.unsigned_abs())),
                 },
                 Bound::Excluded(i) => Bound::Excluded(match i {
                     i if i < 0 => len.saturating_sub(i.unsigned_abs()),


Test patch:
diff --git a/crates/nu-command/tests/commands/str_/mod.rs b/crates/nu-command/tests/commands/str_/mod.rs
index 2aeabfd04ac6d..9a361f7d439dd 100644
--- a/crates/nu-command/tests/commands/str_/mod.rs
+++ b/crates/nu-command/tests/commands/str_/mod.rs
@@ -388,6 +388,13 @@ fn substring_by_negative_index() {
     })
 }
 
+#[test]
+fn substring_of_empty_string() {
+    let actual = nu!("'' | str substring ..0");
+    assert_eq!(actual.err, "");
+    assert_eq!(actual.out, "");
+}
+
 #[test]
 fn str_reverse() {
     let actual = nu!(r#"