-1

I have a struct that contains the return code and the data retrieved from a postgres query. I have a new() function that initializes the struct:

impl <T> Response <T>
     {
     pub fn new() -> Response <T>
             {
             return Response
                 {
                 rc : RetCode::Ok,
 ------>         pg_err : Error::new(),
                 desc : "".to_string(),
                 data : Vec::new(), 
                 id : 0,
                }

but the postgres::Error::new() is private. Is there a way to create a new error ( i don’t mind the type or content), just for placeholder for actual, future error? i don’t need Null, just a fake error, because in RetCode i have ok, no-data-retrieved, postgre error, so i need to check the error just when retcode is pg_error.

Rottame
  • 21
  • 3
  • 2
    Does your response always actually contain an error? If not, don't use a blank/newly created struct to represent "no error" -- use [`Option`](https://doc.rust-lang.org/std/option/enum.Option.html) instead. – Herohtar Aug 31 '22 at 21:42
  • Duplicate of [How to set a field in a struct with an empty value?](https://stackoverflow.com/questions/57962168/how-to-set-a-field-in-a-struct-with-an-empty-value) – Herohtar Aug 31 '22 at 21:48
  • 2
    No, don't make a nullary constructor if there isn't a meaningful value for it. `null`ing out fields is a very old-fashioned Java way of thinking. In Rust, objects should always be in a defined state, not in some nebulous half-empty state (non-`Drop` objects can be partially dropped, but Rust is capable of enforcing the safety on that feature, so that's different) – Silvio Mayolo Aug 31 '22 at 22:00
  • excuse me, i explained not so well. i don’t need a null, i need to instantiate an error, but the new is private. i edited the post for clarity – Rottame Sep 01 '22 at 06:56
  • @Rottame What are you trying to express by creating an `Error` when no error has occurred? The plain answer is you cannot; there is no public way to create a [`postgres::Error`](https://docs.rs/postgres/latest/postgres/error/struct.Error.html) yourself. It sounds like you want an `enum` to express the combination of "ok", "no-data", "error" so that you don't need a `pg_err` if its not an "error" `RetCode`. – kmdreko Sep 01 '22 at 17:10
  • @kmdreko yes, rc: retcode is a enum of {ok, no_data, pg_err}. when i return a retcode=pg_err, the main program knows that has to get the "real" error from pg_err, so i copy the error given by the client.query in the pg_err field. but when i have to return retcode=OK because the function had no errors, i still have to have an initializated pg_error, otherwise the compiler gives me "assign to part of possibly-uninitialized variable: `response`" – Rottame Sep 01 '22 at 21:10

1 Answers1

2

As mentioned in the comments, there is no public way to create a postgres::Error. You can only get one from some other postgres operation that fails. You can use the linked answer and use an Option to create the pg_err with None before an error occurs, and filling it with Some(...) when it does.

However, that is not the way you should solve this particular problem. You have a single type that is designed to express multiple different forms of responses: "ok", "no-data", and "error" as you've mentioned in the comments. So it sounds like you have RetCode designed as such:

enum RetCode { Ok, NoData, Error }

But Rust enums are much more powerful than in other languages, they can hold data exclusive to a specific variant. There is a philosophy to "make invalid states unrepresentable". In other words, why have a pg_err if no error occurred? Why have a data field if the status is NoData?

You can restructure your objects like so:

enum ResponseKind<T> {
    Ok { data: Vec<T> },
    NoData,
    Error { pg_err: postgres::Error },
}

struct Response<T> {
    id: i32,
    desc: String,
    kind: ResponseKind<T>,
}

That way you don't have to worry about creating some dummy error type.

You should heed the other comments; don't fall into the trap of simply creating a dummy constructor just for the sake of it. And new() is just a convention which shouldn't be followed if there's no obvious default state for your type. You should have constructor functions with parameters that provide valid data to construct it with. Like:

impl<T> Response<T> {
    pub fn ok(id: i32, desc: String, data: Vec<T>) -> Response<T> {
        // or maybe id and desc are generated or dependent on
        // kind and don't need to be parameters?
        Response {
            id,
            desc,
            kind: ResponseKind::Ok { data },
        }
    }

    pub fn no_data(id: i32, desc: String) -> Response<T> { ... }
    pub fn error(id: i32, desc: String, error: Error) -> Response<T> { ... }
}
kmdreko
  • 42,554
  • 6
  • 57
  • 106