Conditional creation of a struct instance and handing it mutable Option to fn


#1

I’m quite new to Rust (with a background in some JVM languages) and I’m stuck with the following thing:
It is kind of hard for me to describe what I exactly want to achieve without some context, so I try to provide as much as possible:

Given is a CLI application that uses Clap for argument handling. Based on the fact that a value-taking argument is present (matching the value of the argument against Some/None), I want to create an instance of a struct that holds a value of a rocksdb using the path from the argument. When the argument is not passed, the instance must not be created and therefore no handle to the rocksdb is created.

let opt_log_compact_tracking = match matches.value_of("count-alive-keys") {
    Some(path) => Some(LogCompactionKeyMetrics::new(path)),
    None => None
};

The resulting Some is later to be used by another function to discover, if the feature has been enabled or not in order to call a function on the struct or not.

The function on the struct needs &mut self, therefore I somehow need to pass a mutable ref, wrapped in Some(), to my function (as far as I understood).

I came up with prepending &mut in above’s code Some(&mut LogCompactionKeyMetrics::new(path)),, but this makes the created struct not survive long enough.

Any guidance is highly appreciated!


#2

You have a Option<LogCompactionKeyMetrics> value, by the looks of it. This Option owns the inner value, and you can take it out of there (if you need) or can borrow it mutably to call a &mut self method. So the code in your snippet looks fine, you don’t need to change anything. How are you trying to call this method in question?


#3

The source is available on GH, although the repo state differs from my local state due to not being functional yet. The struct implementation that holds the rocksdb can be found here: https://github.com/xenji/kafka-topic-analyzer/blob/add-log-compaction-metrics/src/metric.rs#L207

The call site looks currently like this (although it is not working at all):

// signature: log_compaction_metrics: &Option<LogCompactionKeyMetrics>,
// ...

if empty_key == false {
                            match log_compaction_metrics {
                                &Some(&mut lcm) => lcm.mark_key_alive(key),
                                None => {}
                            }
                        }

#4

The error from the stable compiler is:

&Some(&mut lcm) => lcm.mark_key_alive(key),
    | ^^^^^^^^ expected struct `metric::LogCompactionKeyMetrics`, found &mut _

#5
match log_compaction_metrics {
                                Some(ref mut lcm) => lcm.mark_key_alive(key),
                                None => {}
                            }

#6

&Option<T> is a useless type. Make sure to use more useful equivalent Option<&T> instead, which you can get with .as_ref()/as_mut().


#7

Thanks for all the hints so far. As far as I understood, the Option<&T> needs the referenced type to outlive the inner block of the creation, is that correct?


#8

Yes, the value that Option<&mut T> is borrowing needs to be alive while this option is.

The one liner for you would be:

log_compaction_metrics.as_mut().map(|lcm| lcm.mark_key_alive(key));

#9

That was the final hint!
Resulting code so far:

let mut opt_log_compact_tracking = match matches.value_of("count-alive-keys") {
        Some(path) => Some(LogCompactionKeyMetrics::new(path)),
        None => None
    };

Passing it:

kafka::read_topic_into_metrics(topic, &consumer, &mut mr, &mut opt_log_compact_tracking, &partitions, &end_offsets);

Use-site:

if empty_key == false {
    log_compaction_metrics.as_mut().map(|lcm| lcm.mark_key_alive(key));
}

#10

Thanks a lot for your help @vitalyd and @kornel!


#11

This is splitting hairs a bit, but it’d be more idiomatic to pass it as opt_log_compact_tracking.as_mut() rather than as &mut opt_log_compact_tracking (and hence the function takes a Option<&mut LogCompactionKeyMetrics> parameter). The use site would then just be log_compaction_metrics.map(...).


#12

Well, that looks better, but introduces the following error:

log_compaction_metrics.map(|lcm| lcm.mark_key_alive(key));
^^^^^^^^^^^^^^^^^^^^^^ value moved here in previous iteration of loop

I thought I had understood the basics of ownership in rust :frowning:

I think the map function moves the ownership, as it uses self, not &self in it’s signature, is that correct?


#13

Yeah, that’s right. I didn’t realize there was a loop at the use site. Then just continue doing as_mut() there before the map().


#14

Ok, perfect. The final “solution” is now here: https://github.com/xenji/kafka-topic-analyzer/blob/add-log-compaction-metrics/src/kafka.rs#L44

Thanks again for your help!