Argument must be a string literal... What, why?

String literal, What is this error all about:

use std::process::Command;

fn main() {

    let output = Command::new("pwd")
                     .output()
                     .expect("failed to execute process");
    let out = std::string::String::from_utf8(output.stdout)
                      .ok()
                      .expect("Failed to read");
    let a = "/my.payload".to_string();
    let st = format!("{}{}", out.trim(), a);

    let my_str = include_str!(st);
    println!("{}", my_str);
}

And the error:

error: argument must be a string literal
  --> src/main.rs:18:28
   |
18 |     let my_str = include_str!(st);
   |                               ^^

I do not understand what is wrong here

include_str! works at compile time, statically inserting the file contents as a string literal in your code, so it can't use a runtime variable. Use a File and read_to_string() to do it at runtime.

Also, starting a process for pwd is rather heavy -- try env::current_dir() instead.

7 Likes

ok, that makes a lot of sense :slight_smile: thnx... But what if i wanna have my file read at compile time ... consider the following situation: I have one app that is generating a simple txt file and then once generated i need to issue a command to compile another tool with the content of that file inside so when executed it prints out the content... any hints on how to do that ?

Sounds like you might be interested in cargo build scripts.

3 Likes

ok ... did not know about that thnx (learning by examples -> this never came up :slight_smile: ) but still it looks like i need to do some sort of regex as pre-processin step to modify main.rs and then call build.rs to compile main.rs... is there a simpler way ?

Update: I might be wrong.. and i probably am but i don't see it :slight_smile:

I don't think you should need to modify main.rs. Most of the time it suffices to have the build script create a .rs file inside env!("OUT_DIR") containing the stuff that can change, and read it using e.g.

// main.rs

include!(concat!(env!("OUT_DIR"), "/generated.rs"));
1 Like

I sincerely do not wanna waste your time, so if you find the post annoying just ignore it. Thank you anyways.
But i still think i need to pre-modify /generated.rs given that in that app i need to have my txt file loaded into at compile time ... or am I wrong...

Your build.rs should create that generated.rs, with whatever modifications you want. The build script can read your text file and generate anything from it. The actual compilation of that file will happen via main.rs when you include it.

1 Like

would you say this is a correct formulation :slight_smile:

// build.rs

use std::env;
use std::fs::File;
use std::io::Write;
use std::path::Path;
use std::env::current_dir;


fn main() {
    
    let mut out_dir = current_dir().unwrap();
    out_dir.push("./src/main.rs");

    let dest_path = out_dir.into_os_string().into_string().unwrap();
    let mut f = File::create(&dest_path).unwrap();

    f.write_all(b"
fn main() {

	let my_str = include_str!(\"../my.payload\");
	println!(\"{}\", my_str);  
}
    ").unwrap();
}

I mean it does what I want but how badly is it written / done ?

I would formulate it slightly differently. Your build script generates a file inside the $OUT_DIR directory (e.g. $OUT_DIR/my.payload), then your main.rs would include that as a string constant with include_str!(concat!(env!("OUT_DIR"), "/my.payload")).

You don't want to edit your main.rs itself. For one, it means the generated data will end up in git and add loads of unnecessary changes to the history, but more importantly it means whenever you want to make changes to main.rs you'll need to open up build.rs and edit a string constant. That could be a pain to maintain.

For example:

// build.rs

use std::path::Path;
use std::fs::File;
use std::io::Result;

fn main() {
  // the compiler tells us which directory to write generated code to
  let out_dir = std::env::var("OUT_DIR").unwrap();
  let payload_file = Path::from(out_dir).join("my.payload");

  // open the file for writing
  let mut f = File::create(&payload_file).unwrap();

  // then write the generated information to it
  writeln!(f, "This is some content").unwrap();
  writeln!(f, "Answer to the Ultimate Question of Life, the Universe, and Everything = {}", 42).unwrap();
}

And then you can use the generated artefact from your main.rs.

// main.rs

const MY_PAYLOAD: &'static str = include_str!(concat!(env!("OUT_DIR"), "/my.payload"));

fn main() {
  println!("Payload: {}", MY_PAYLOAD);
}

Note that the write!() and writeln!() macros are like println!() in that they let you do string formatting, except it'll write to some "writer" (e.g. a file) instead of your console.file).

5 Likes