"panic!()": compiles if used directly, but not indirectly from a function

Hi - brand new Rust programmer here :stuck_out_tongue: (sorry if this is an incredibly stupid question + the code is weird + etc... ).

I have the following struct (focus on the commented line "panic!("from struct");")...

pub struct DbPgsqlMain
{
SOME VARIABLES HERE
}
impl DbPgsqlMain
{
	pub fn new(hostname:String, port:u16, schema:String, username:String, userpwd:String) -> DbPgsqlMain
	{
		let conn_parms:String = format!(
			"host={} dbname={} user={} password={} port={} connect_timeout=60 keepalives=1 keepalives_idle=60",
			hostname, schema, username, userpwd, port);
		printmsg(0, false, format!("Trying to connect to the DB \"{}\" on host \"{}\".", &schema, &hostname));
		match postgres::Client::connect(conn_parms.as_str(),postgres::NoTls)
		{
			Ok(connection) =>
			{
				printmsg(1, false, format!("Connected successfully to the DB \"{}\" on host \"{}\".", &schema, &hostname));
				return DbPgsqlMain {
					SOME PARAMETERS HERE
				};
			}
			Err(e) =>
			{
				printmsg(1, true, format!("Connection attempt to DB \"{}\" on host \"{}\" failed with the error\"{}\".", &schema, &hostname, e));
				//panic!("from struct");
			}
		};
	}
}

... and I have the following function (focus on the line "{ panic!("from func"); }"):

pub fn printmsg<S>(idt:usize, is_error:bool, msg:S)
where S: Into<String>
{
	let mut output:String = String::from("TODO INSERT HERE TIMESTAMP ");
	
	//Add leading indentation
	output.push_str(" ".repeat(idt).as_str());
	
	if is_error == true
		{ output.push_str("ERROR - "); }
	
	output.push_str(msg.into().as_str());
	
	println!("{}", output);
	if is_error == true
		{ panic!("from func"); }
}

The above will NOT compile unless I uncomment the line "panic!("from struct");" in the struct method "new()" of DbPgsqlMain => why? Respectively, what should I do to be able to avoid the extra "panic!("from struct");"-call in the struct?

I understand on a high level the compilation error message "expected struct DbPgsqlMain, found ()" as I'm not returning anything in that ("Err()") portion of code, but anyway as I hardcode "is_error" to "true" in the function call the endresult through the function is the same (calling "panic!") as calling "panic!" directly... .

Thanks :slight_smile:

The compiler doesn't follow the control flow, and doesn't know you'll always panic. It treats the function as a black box.

4 Likes

A diverging expression -- such as an expression that panics -- has the "never" type !. This type can be coerced into any other type (since the compiler understands that program execution doesn't actually continue). When you uncomment the line, the compiler understands that execution will end in your Err branch, and allows you to not return a DbPgsqlMain there.

That would require the compiler to analyze the body of printmsg<String> with the context of the parameters you pass into it, and then treat the resulting behavior as a promise of printmsg's API. If it were to do this, you later might change printmsg to not panic without changing the API, and yet it would cause far away code to break.

This is undesirable, so instead, the API of your function is the contract. The compiler isn't going to assume anything beyond that contract, so that far away code can rely on it.

The best solution is going to be removing the conditional panic, in my opinion. Have two different helper functions.

// I made a couple other changes beyond the scope of your question
// which you may wish to consider
pub fn printmsg<S: AsRef<str>>(idt: usize, msg: S) {
    let mut output: String = String::from("TODO INSERT HERE TIMESTAMP ");
    output.push_str(msg.as_ref());

    println!("{:width$}{}", "", output, width = idt);
}

// The compiler will understand that this diverges due to the return type
pub fn abort_with_msg<S: AsRef<str>>(idt: usize, msg: S) -> ! {
    let msg = format!("Error - {}", msg.as_ref());
    printmsg(idt, msg);
    panic!("from func")
}

Try it on the playground.

Alternatively, instead of the panic!(...), you could put an unreachable!().

4 Likes

Well, arguably the best solution involves using Result instead of panic in new, but that's a whole other topic.

3 Likes

Thanks a lot!!! All your replies/explanations absolutely helped to understand how the compiler works, what that "!" return type is (which I previously saw being mentioned but didn't really understand how to use) and raised the point about using "Result" :+1:

I then did initially go the way of implementing "Result" but the code ended up becoming a little bit difficult to read.
After debating for a while in my mind I tried out the variant with the 2 separate helper functions => the code that uses them looks leaner, and I can get rid of the true/false parameter (thx as well about showing the "AsRef" usage) => I'll stick to this.

Again, thanks a looot, cheers!! :smiley:

This topic was automatically closed 90 days after the last reply. We invite you to open a new topic if you have further questions or comments.