# Idea: Dedented string literals

**URL:** <https://internals.rust-lang.org/t/idea-dedented-string-literals/23002>\
**Category:** language design\
**Created:** [May 28, 2025, 3:11am UTC](https://internals.rust-lang.org/t/idea-dedented-string-literals/23002 "2025-05-28T03:11:09Z")\
**Posts on this page:** 20\
**Page:** 1

<div class="post-metadata">

**Author:** ![nik-rev](https://sea2.discourse-cdn.com/flex002/user_avatar/internals.rust-lang.org/nik-rev/32/13034_2.png) [@nik-rev](https://internals.rust-lang.org/u/nik-rev)\
**Post date:** [May 28, 2025, 3:11am UTC](https://internals.rust-lang.org/t/idea-dedented-string-literals/23002/1 "2025-05-28T03:11:09Z")

</div>

Compile-time dedented string literals.

Right now this is just an idea. If it is liked, I'll make an RFC.

## Problem

Embedding formatted string literals requires making a choice:

- sacrifice readability of the source code
- sacrifice readability of the output

```rs
fn main() {
    println!("
        create table student(
            id int primary key,
            name text
        )
    ");
}

```

This outputs (using `^` to mark the beginning of a line and `·` to mark a leading space):

```sql
^
^········create table student(
^············id int primary key,
^············name text
^········)
^····

```

For the output to look sensible, we have to sacrifice on readability of the code:

```rs
fn main() {
    println!("create table student(
    id int primary key,
    name text
)");
}

```

This produces the expected:

```sql
create table student(
  id int primary key,
  name text
)

```

Why can we not have the best of both worlds?

## Solution: Dedented string literals

The new string modifier `d"my_string"` (similar to `b"str"`, `br"str"`, etc.) un-indents the string literal at compile time so the leftmost non-space character is in the first column

Our problems above would be fixed by using a dedented string literal.

```rs
fn main() {
    println!(d"
        create table student(
            id int primary key,
            name text
        )
    ");
}

```

The above will output:

```sql
create table student(
  id int primary key,
  name text
)

```

## More Examples

```rs
fn main() {
    let testing = d"
        def hello():
            print('Hello, world!')

        hello()
    ";
    let expected = "def hello():\n print('Hello, world!')\n\nhello()";
    assert_eq!(testing, expected);
}

```

Works with raw string literals:

```rust
fn main() {
    let testing = dr#"
        def hello():
            print("Hello, world!")

        hello()
    "#;
    let expected = "def hello():\n print(\"Hello, world!\")\n\nhello()";
    assert_eq!(testing, expected);
}

```

Works with byte string literals:

```rs
fn main() {
    let testing = db"
        def hello():
            print('Hello, world!')

        hello()
    "#;
    let expected = b"def hello():\n print('Hello, world!')\n\nhello()";
    assert_eq!(testing[..], expected[..]);
}

```

## Exact behaviour

- The opening line (everything immediately right of the opening `"`) must contain only a literal newline character.
- The opening line's literal newline is removed.
- The closing line (everything immediately to the left of the closing `"`) may contain whitespace, but the whitespace is removed.
- A single literal newline character before closing line is removed if it exists.
- The common indentation of all lines (other than the opening or closing line) that do not fully consist of whitespace is calculated.
- That common indentation is removed from the start of every line.

Creating strings that have an indentation on every line is not supported.

* * *

This is similar to the `indoc!` crate, but included in the language.

Why I believe this should become a language feature:

- It is widely used. 110 million downloads on [crates.io](http://crates.io)
- Avoid a dependency for a feature that can be commonly used and helpful.
- Increases discoverability of the feature. Code samples that may not have previously depended on a crate can utilise it
- Dedented string literals make code more legible. I assume people are not going to always add a crate for this feature, so they're going to have to sacrifice code legibility.
- It fits with the current "string modifiers" that Rust has, stacking with them.
- Dedented strings can be formatted by `rustfmt` to have 1 more level of indentation than the surrounding code

Drawbacks:

- Increases language complexity. I believe it should not be a large increase, and it is worth it.

Prior art:

- _Java_ - [text blocks](https://openjdk.java.net/jeps/378) using triple-quotes.
- _Kotlin_ - [raw strings](https://kotlinlang.org/docs/strings.html#raw-strings) using triple-quotes and `.trimIndent()`.
- _Scala_ - [multiline strings](https://docs.scala-lang.org/overviews/scala-book/two-notes-about-strings.html) using triple-quotes and `.stripMargin`.
- C# - [Raw string literals](https://learn.microsoft.com/en-us/dotnet/csharp/language-reference/tokens/raw-string)
- _Python_ - [multiline strings](https://docs.python.org/3/library/textwrap.html) using triple-quotes to avoid escaping and `textwrap.dedent`.
- _Jsonnet_ - [text blocks](https://jsonnet.org/learning/tutorial.html) with `|||` as a delimiter.
- _Bash_ - [`<<-` Heredocs](https://pubs.opengroup.org/onlinepubs/9699919799/utilities/V3_chap02.html#tag_18_07_04).
- _Ruby_ - [`<<~` Heredocs](https://www.rubyguides.com/2018/11/ruby-heredoc/).
- _Swift_ - [multiline string literals](https://docs.swift.org/swift-book/LanguageGuide/StringsAndCharacters.html#ID286) using triple-quotes - strips margin based on whitespace before closing delimiter.

I have taken a lot [from a very similar JavaScript proposal](https://github.com/tc39/proposal-string-dedent/blob/master/README.md?plain=1)

---

<div class="post-metadata">

**Author:** ![scottmcm](https://sea2.discourse-cdn.com/flex002/user_avatar/internals.rust-lang.org/scottmcm/32/2355_2.png) [@scottmcm](https://internals.rust-lang.org/u/scottmcm)\
**Post date:** [May 28, 2025, 4:58am UTC](https://internals.rust-lang.org/t/idea-dedented-string-literals/23002/2 "2025-05-28T04:58:51Z")

</div>

Another prior art: [Raw string literals - """ - C# reference | Microsoft Learn](https://learn.microsoft.com/en-us/dotnet/csharp/language-reference/tokens/raw-string)

Previous Rust conversation: [Propose code string literals by Diggsey · Pull Request #3450 · rust-lang/rfcs · GitHub](https://github.com/rust-lang/rfcs/pull/3450)

> [@nik-rev](#):
>
> The "common indentation" of all non-empty content lines (lines that are not the opening or closing) are calculated.

Note that that makes it hard to make something that _is_ indented if that was desired.

FWIW that C# version allows

```cs
Foo(1, 2, 3, """
        Hello
            World
    """);

```

for a string where _both_ lines are indented (the first by 4 the second by 8).

---

<div class="post-metadata">

**Author:** ![mathstuf](https://avatars.discourse-cdn.com/v4/letter/m/958977/32.png) [@mathstuf](https://internals.rust-lang.org/u/mathstuf)\
**Post date:** [May 28, 2025, 5:48am UTC](https://internals.rust-lang.org/t/idea-dedented-string-literals/23002/3 "2025-05-28T05:48:00Z")

</div>

I'll apply Cunningham's Law here. How about the opening line be able to apply a uniform level of indentation, so you'd have:

```rust
Foo(1, 2, 3, d" \
        Hello
            World
    ");

```

Where the ` \` applies 4 spaces to the entire block.

---

<div class="post-metadata">

**Author:** ![josh](https://sea2.discourse-cdn.com/flex002/user_avatar/internals.rust-lang.org/josh/32/5934_2.png) [@josh](https://internals.rust-lang.org/u/josh)\
**Post date:** [May 28, 2025, 5:58am UTC](https://internals.rust-lang.org/t/idea-dedented-string-literals/23002/4 "2025-05-28T05:58:15Z")

</div>

This is exactly the kind of bikeshedding that led to the previous proposal stalling out.

I think it might be OK if this mechanism _doesn't_ support strings where the entire string is indented. Hopefully it won't be that hard to write helper functions for const-eval-time indentation.

---

<div class="post-metadata">

**Author:** ![jjpe](https://sea2.discourse-cdn.com/flex002/user_avatar/internals.rust-lang.org/jjpe/32/9779_2.png) [@jjpe](https://internals.rust-lang.org/u/jjpe)\
**Post date:** [May 28, 2025, 6:43am UTC](https://internals.rust-lang.org/t/idea-dedented-string-literals/23002/5 "2025-05-28T06:43:35Z")

</div>

In and of itself, I'd love a mechanism that could automatically dedent string contents.

However, a concern I see here is its interaction with raw and C-style strings: if we wanted e.g. a dedented raw string, we'd need something like

```rust
rd"hello
world"

```

That is, the combinatorics of adding more string types are unfortunately not working in our favor here. I'm not saying it couldn't be done (the number of combinations is at this time still fairly low) but it's something that seriously needs to be considered for each string type added.

---

<div class="post-metadata">

**Author:** ![DragonDev1906](https://avatars.discourse-cdn.com/v4/letter/d/e68b1a/32.png) [@DragonDev1906](https://internals.rust-lang.org/u/DragonDev1906)\
**Post date:** [May 28, 2025, 9:32am UTC](https://internals.rust-lang.org/t/idea-dedented-string-literals/23002/6 "2025-05-28T09:32:31Z")

</div>

> [@jjpe](#):
>
> but it's something that seriously needs to be considered for each string type added.

Yes, though isn't each prefix just a modifier that is applied right-to-left (at least in concept)?

- `'...'`: Start with a single character/rune
- `"..."` Start with multiple characters
- `r`, `r#`, `r##`, ...: Extend the literal unless it is followed by this many `#`
- **`d`: Remove common indentation and leading/trailing newlines**
- `b`: Interpret the characters as bytes and return a `[u8; _]`/`u8` instead of a `str`/`rune`
- `c`: Forbid `0x00` and append `0x00`

As long as these are seen/parsed as individual modifiers, I don't see a big issue with combinatorical explosion (not sure about the impact on parsing difficulty). The number of possible output types does increase of course:

- `o`: Make the output owned (heap allocated): `Vec<u8>`, `String`, `CString` (syntactical suggar)
- `f`: Treat the characters like a format string (see `format!` macro) - Quite possible that this isn't a good idea because the result isn't a literal anymore (and thus forces to be heap allocated)
- `l`: Make all characters lowercase
- `u`: Make all characters uppercase
- `s`: Strip all whitespaces

That way you (for example) wouldn't need to write `"something".to_owned()`:

- `ocr#"something"#` == `c"something".to_owned()`
- `obudr#"\n something\n else\n "#` == `b"SOMETHING\nELSE".to_owned()` - Read: Owned Bytes Uppercased Dedented Raw String \[1\]

Note that I'm not proposing to add those, (except perhaps for `o`) they're mainly for showing my point of them being modifiers. Especially `u` and `l` are probably not useful enough to be worth it, but `o` \[2\] and `f` could be really useful.

In theory even `b` and `c` could be combined: `bc"test"`: Take the bytes of the cstring c"test", returning the same as `b"test\x00"`, though since both impact the return type that might be more difficult/problematic to implement.

* * *

1. I've placed them after `b` here, as they can modify the characters independent of whether they are a string, cstring or bytes. 

2. `o` (Owned) is one that'd also make sense as a postfix, but for that we also have the more verbose `.to_owned()`

---

<div class="post-metadata">

**Author:** ![bungcip](https://sea2.discourse-cdn.com/flex002/user_avatar/internals.rust-lang.org/bungcip/32/351_2.png) [@bungcip](https://internals.rust-lang.org/u/bungcip)\
**Post date:** [May 30, 2025, 6:15pm UTC](https://internals.rust-lang.org/t/idea-dedented-string-literals/23002/7 "2025-05-30T18:15:59Z")

</div>

how about using multiline string like moonbit:

```rust
let str =
  #| def hello():
  #| print('hello world')
  #|
  #| hello()
  ;

```

---

<div class="post-metadata">

**Author:** ![josh](https://sea2.discourse-cdn.com/flex002/user_avatar/internals.rust-lang.org/josh/32/5934_2.png) [@josh](https://internals.rust-lang.org/u/josh)\
**Post date:** [May 30, 2025, 8:56pm UTC](https://internals.rust-lang.org/t/idea-dedented-string-literals/23002/8 "2025-05-30T20:56:29Z")

</div>

> [@nik-rev](#):
>
> The common indentation of all lines (other than the opening or closing line) that do not fully consist of whitespace is calculated.

I think this needs clarification. The behavior I would expect would be "The common indentation of all non-empty lines", rather than caring about lines consisting entirely of whitespace.

---

<div class="post-metadata">

**Author:** ![miikkas](https://avatars.discourse-cdn.com/v4/letter/m/d2c977/32.png) [@miikkas](https://internals.rust-lang.org/u/miikkas)\
**Post date:** [May 31, 2025, 9:01am UTC](https://internals.rust-lang.org/t/idea-dedented-string-literals/23002/9 "2025-05-31T09:01:39Z")

</div>

I think this is _by far_ the neatest of the alternatives.

Some points I see in favor:

- It solves the problem of retaining some indentation on the lines:

```rust
let my_python_code =
    #| # ...snip...
    #| def python_method(self):
    #| do_stuff()
    ;

```

- It also solves the problem of stripping a possible extra newline from the very beginning of the string.
- As for the problem of deciding whether to have a trailing newline, an empty `#| `line can just be added, e.g.:

```rust
let my_python_code =
    #| # ...snip...
    #| def python_method(self):
    #| do_stuff()
    #| 
    ;

```

- Solves having double quotes in the literal.
- You could even add comments in between the lines of the literal that are not part of the final string, if you want to:

```rust
static HTML_HEADER: &str =
  // my-code-editor-plugin: syntax-hl(html)
  #| <head>
  // TODO: add our code here
  #| <script>
  // my-code-editor-plugin: syntax-hl(js)
  #| console.log("hello");
  #| </script>
  #| </head>
  // let's just have the endline character here for now
  #| 
  ;

```

Matter of opinion, but I happen to like:

- Rust users are already used to each docstring line beginning with a `///` or `//!`. Beginning each line of the string with a `#|` (or `#| ` with first space not included in the string – even better IMO) has that similar ring to it.
- I find this alternative really readable.
  - No one-letter prefix to remember/look up.
  - No delimiters.

---

<div class="post-metadata">

**Author:** ![pitaj](https://sea2.discourse-cdn.com/flex002/user_avatar/internals.rust-lang.org/pitaj/32/11262_2.png) [@pitaj](https://internals.rust-lang.org/u/pitaj)\
**Post date:** [May 31, 2025, 2:00pm UTC](https://internals.rust-lang.org/t/idea-dedented-string-literals/23002/10 "2025-05-31T14:00:04Z")

</div>

Yeah I think this is great option, except I would still like some way to compose it with raw strings, C strings, byte strings, and possibly format strings in the future. So I think it should still have quotes and a prefix:

```rust
let my_python_code = d"
    #| # ...snip...
    #| def python_method(self):
    #| do_stuff()
    #| 
    ";

// raw version
let my_powershell_command = dr"
    #| Do-Something .\some\file\path
    #| Then-Another-Thing
    #| 
    ";

```

Finally, with the quotes, the leading `#` is unnecessary, so use just a pipe:

```rust
let my_python_code = d"
    | # ...snip...
    | def python_method(self):
    | do_stuff()
    | 
    ";

```

---

<div class="post-metadata">

**Author:** ![pdolezal](https://sea2.discourse-cdn.com/flex002/user_avatar/internals.rust-lang.org/pdolezal/32/7137_2.png) [@pdolezal](https://internals.rust-lang.org/u/pdolezal)\
**Post date:** [June 1, 2025, 8:35pm UTC](https://internals.rust-lang.org/t/idea-dedented-string-literals/23002/11 "2025-06-01T20:35:48Z")

</div>

There is nothing wrong per se with having the whole string indented. Java allows it and it works just fine IMO. Regarding the precise details, [the Java Language Specification](https://docs.oracle.com/javase/specs/jls/se17/html/jls-3.html#jls-3.10.6) provides them too.

I'd personally prefer it to having an explicit start of the line suggested above, because it is easier just to copy and paste the values. On the other hand, I agree it that the explicit start of the text block line, well, is explicit and better readable.

Perhaps, it might be valuable to have the _option_ to choose the line-starting character in a way similar to the option to choose raw string terminating sequence. Ideally, it should be possible to apply it to all possible string literals, as wished above too.

---

<div class="post-metadata">

**Author:** ![Dwarsloeper](https://sea2.discourse-cdn.com/flex002/user_avatar/internals.rust-lang.org/dwarsloeper/32/11073_2.png) [@Dwarsloeper](https://internals.rust-lang.org/u/Dwarsloeper)\
**Post date:** [June 1, 2025, 8:48pm UTC](https://internals.rust-lang.org/t/idea-dedented-string-literals/23002/12 "2025-06-01T20:48:12Z")

</div>

You actually gave a solution to your format dilemma. It would **not** go to the heap, and thus only be allowed to include statically known elements:

```rust
const STR: &str = "STR";
let str = "str";
let str_str: &'static str = f"{STR} {str}";

```

No such restriction would apply to the owned variant:

```rust
let string = str.to_string();
let string_str: String = of"{string} {str}";

```

---

<div class="post-metadata">

**Author:** ![danjl1100](https://sea2.discourse-cdn.com/flex002/user_avatar/internals.rust-lang.org/danjl1100/32/6466_2.png) [@danjl1100](https://internals.rust-lang.org/u/danjl1100)\
**Post date:** [June 2, 2025, 1:24am UTC](https://internals.rust-lang.org/t/idea-dedented-string-literals/23002/13 "2025-06-02T01:24:54Z")

</div>

Another link for prior art:

- Nix language - [double single quotes `''`, indented string literal ("the second way")](https://nix.dev/manual/nix/2.29/language/string-literals.html)

That also uses the common leading whitespace of nonempty lines to determine how much to remove.

---

<div class="post-metadata">

**Author:** ![DragonDev1906](https://avatars.discourse-cdn.com/v4/letter/d/e68b1a/32.png) [@DragonDev1906](https://internals.rust-lang.org/u/DragonDev1906)\
**Post date:** [June 2, 2025, 8:40am UTC](https://internals.rust-lang.org/t/idea-dedented-string-literals/23002/14 "2025-06-02T08:40:05Z")

</div>

> [@pdolezal](#):
>
> Perhaps, it might be valuable to have the _option_ to choose the line-starting character in a way similar to the option to choose raw string terminating sequence

Probably not needed, as it can just be the first non-whitespace character (regardless of what it is + enforcement that they are the same) on the next line (unless not using a line-starting character should be possible, too).

---

<div class="post-metadata">

**Author:** ![psichokenetic](https://sea2.discourse-cdn.com/flex002/user_avatar/internals.rust-lang.org/psichokenetic/32/13176_2.png) [@psichokenetic](https://internals.rust-lang.org/u/psichokenetic)\
**Post date:** [June 3, 2025, 3:57am UTC](https://internals.rust-lang.org/t/idea-dedented-string-literals/23002/15 "2025-06-03T03:57:27Z")

</div>

More prior art from nix.

```nix
              installPhaseCommand = ''
                mkdir -p $out/bin
                cp target/release/${name} $out/bin/
                cp -r target/site $out/bin/
                wrapProgram $out/bin/${name} \
                  --set LEPTOS_SITE_ROOT $out/bin/site \
              '';

```

NIx is not a language designed for everything, but it was very much designed for gluing together and interpolating bits of bash. This behavior makes me jealous when using languages that don't have it. It is a beloved feature.

---

<div class="post-metadata">

**Author:** ![curoli](https://sea2.discourse-cdn.com/flex002/user_avatar/internals.rust-lang.org/curoli/32/11357_2.png) [@curoli](https://internals.rust-lang.org/u/curoli)\
**Post date:** [June 3, 2025, 9:21pm UTC](https://internals.rust-lang.org/t/idea-dedented-string-literals/23002/16 "2025-06-03T21:21:07Z")

</div>

Scala has a function [`stripMargin`](https://www.scala-lang.org/api/2.12.7/scala/collection/immutable/StringLike.html#stripMargin:String) to get rid of extra indentation.

---

<div class="post-metadata">

**Author:** ![toc](https://sea2.discourse-cdn.com/flex002/user_avatar/internals.rust-lang.org/toc/32/6692_2.png) [@toc](https://internals.rust-lang.org/u/toc)\
**Post date:** [June 3, 2025, 9:36pm UTC](https://internals.rust-lang.org/t/idea-dedented-string-literals/23002/17 "2025-06-03T21:36:47Z")

</div>

> [@josh](#):
>
> Hopefully it won't be that hard to write helper functions for const-eval-time indentation.

One question I have is whether better const eval support can happen faster than this particular bikeshed can be painted. Then flavors of string dedentation can be prototyped in libraries (and possibly eventually promoted to std). Something like

```rust
const fn dedented(s: &str) -> &'static str {
    let s = s.to_string();
    // ... dedentate it
    s.leak()
}

```

```rust
use strs::dedented as ds;

fn main() {
    println!(
        ds(####"
            create table student(
                id int primary key,
                name text
            )
        "####)
    );
}

```

---

<div class="post-metadata">

**Author:** ![jdahlstrom](https://sea2.discourse-cdn.com/flex002/user_avatar/internals.rust-lang.org/jdahlstrom/32/3351_2.png) [@jdahlstrom](https://internals.rust-lang.org/u/jdahlstrom)\
**Post date:** [June 4, 2025, 12:36pm UTC](https://internals.rust-lang.org/t/idea-dedented-string-literals/23002/18 "2025-06-04T12:36:25Z")

</div>

Might be cool if string literals had a _const-only_ coercion to `&mut str`. Then you wouldn’t even need const alloc support as dedent can only shrink a string.

---

<div class="post-metadata">

**Author:** ![madsmtm](https://sea2.discourse-cdn.com/flex002/user_avatar/internals.rust-lang.org/madsmtm/32/8455_2.png) [@madsmtm](https://internals.rust-lang.org/u/madsmtm)\
**Post date:** [June 4, 2025, 1:34pm UTC](https://internals.rust-lang.org/t/idea-dedented-string-literals/23002/19 "2025-06-04T13:34:23Z")

</div>

A bit more prior art in the Rust crate ecosystem: [`textwrap_macros::dedent!`](https://docs.rs/textwrap-macros/0.3.0/textwrap_macros/macro.dedent.html) and [`dedent::dedent!`](https://docs.rs/dedent/0.1.1/dedent/macro.dedent.html).

---

<div class="post-metadata">

**Author:** ![jdahlstrom](https://sea2.discourse-cdn.com/flex002/user_avatar/internals.rust-lang.org/jdahlstrom/32/3351_2.png) [@jdahlstrom](https://internals.rust-lang.org/u/jdahlstrom)\
**Post date:** [June 4, 2025, 1:59pm UTC](https://internals.rust-lang.org/t/idea-dedented-string-literals/23002/20 "2025-06-04T13:59:05Z")

</div>

Both seem to depend on `syn`, unfortunately. Naively, it shouldn't be too difficult to write a proc macro that takes and returns a single string literal without requiring `syn`, but I don't know much about proc macros.

[Next page](https://internals.rust-lang.org/t/idea-dedented-string-literals/23002.md?page=2)
