0

This is valid code:

use std::rc::Rc;
use std::sync::{Arc, Mutex};

fn foo(n: i32) {
    println!("n = {}", n)
}

fn main() {
    let a = 1;
    let b = Rc::new(2);
    let c = Mutex::new(3);
    let d = Arc::new(Mutex::new(4));

    foo(a);
    foo(*b);
    foo(*(c.lock().unwrap()));
    foo(*((*d).lock().unwrap()));
}

Are there any traits (or anything else) that I can implement so that the function calls could simply become:

foo(a);
foo(b);
foo(c);
foo(d);

What is the Rust idiomatic way for handling the actual data and not caring about how the data is protected/wrapped?

Shepmaster
  • 388,571
  • 95
  • 1,107
  • 1,366
Al Bundy
  • 653
  • 1
  • 6
  • 22
  • 5
    So you are happy with introducing arbitrary panics into your code? – Shepmaster Mar 08 '21 at 15:57
  • No I am not. I just do not know how to write functions in such a way that they can be easily reused no matter if the core type is somehow protected or not. I like how rusts wraps data in such a way that it is not possible to access it without for example locking mutex. However, I would like it to automatically add lock/unlock code. – Al Bundy Mar 08 '21 at 16:01
  • 2
    Your example doesn't just "add lock/unlock code" — it adds an **`unwrap()`**, which can panic. How do you want that handled? – Shepmaster Mar 08 '21 at 16:03
  • It may be possible to make the function sufficiently generic to work for several smart pointers/containers (`&T`, `Rc`, `Box`, etc.). But once `Mutex` is in the picture... this one is not in the same type family, and it should not be used like the others. – E_net4 Mar 08 '21 at 16:03
  • @E_net4couldusemoreflags In this case it's just copying data out of the mutex, so it's fine here. You can just bound the trait on `Copy`. – Aplet123 Mar 08 '21 at 16:04
  • Lets first assume unwrap() will not panic. – Al Bundy Mar 08 '21 at 16:04
  • So what is the typical way to handle mutex protected data in such case? Have one function operating on core data, and the second function being a wrapper for the first one operating on data protected with mutex? – Al Bundy Mar 08 '21 at 16:06
  • 2
    the typical way is to lock properly, watch for error and call the function. – Stargateur Mar 08 '21 at 16:12
  • 1
    It's not idiomatic to ignore wrappers in Rust at all. If you have an `Arc>` and you want to call `foo`, the fallibility of `unwrap` is *your* problem, not the writer of `foo`'s -- it's not a good idea to hide that failure inside a trait and force `foo` to deal with it. – trent Mar 08 '21 at 17:44
  • @AlBundy yes, the "inner" function can take an `&T` or a `Deref`, and the caller would be responsible for handing that out e.g. cloning Rcs, locking mutexes, etc... – Masklinn Mar 09 '21 at 07:15

3 Answers3

2

As others have pointed out, it is a bad idea to ignore the fallibility of Mutex::lock. However for the other cases, you can make your function accept owned values and references transparently by using Borrow:

use std::borrow::Borrow;
use std::rc::Rc;

fn foo (n: impl Borrow<i32>) {
    println!("n = {}", n.borrow())
}

fn main() {
    let a = 1;
    let b = Rc::new (2);
    let c = &3;

    foo (a);
    foo (b);
    foo (c);
}

Playground

Jmb
  • 18,893
  • 2
  • 28
  • 55
1

Here is an extremely literal answer to your specific question. I wouldn't use it.

use std::{
    rc::Rc,
    sync::{Arc, Mutex},
};

fn foo(n: impl DontCare<Output = i32>) {
    let n = n.gimme_it();
    println!("n = {}", n)
}

fn main() {
    let a = 1;
    let b = Rc::new(2);
    let c = Mutex::new(3);
    let d = Arc::new(Mutex::new(4));

    foo(a);
    foo(b);
    foo(c);
    foo(d);
}

trait DontCare {
    type Output;

    fn gimme_it(self) -> Self::Output;
}

impl DontCare for i32 {
    type Output = Self;

    fn gimme_it(self) -> Self::Output {
        self
    }
}

impl<T: DontCare> DontCare for Mutex<T> {
    type Output = T::Output;

    fn gimme_it(self) -> Self::Output {
        self.into_inner()
            .expect("Lets first assume unwrap() will not panic")
            .gimme_it()
    }
}

impl<T: DontCare> DontCare for Rc<T> {
    type Output = T::Output;

    fn gimme_it(self) -> Self::Output {
        match Rc::try_unwrap(self) {
            Ok(v) => v.gimme_it(),
            _ => unreachable!("Lets first assume unwrap() will not panic"),
        }
    }
}

impl<T: DontCare> DontCare for Arc<T> {
    type Output = T::Output;

    fn gimme_it(self) -> Self::Output {
        match Arc::try_unwrap(self) {
            Ok(v) => v.gimme_it(),
            _ => unreachable!("Lets first assume unwrap() will not panic"),
        }
    }
}
  1. The function signatures you've specified take ownership of the value. That will be highly painful, especially paired with any type that doesn't implement Copy.

  2. There are a number of code paths that panic implicitly. I'm not a fan of baking in panics — I reserve that for algorithmic failures, not data-driven ones.

See also:

Shepmaster
  • 388,571
  • 95
  • 1,107
  • 1,366
  • And what is the way you would use? What am I missing here? I often see functions with following signatures `pub fn new(f: Arc)` or even `Arc>`, however these functions do not look to be generic as they do not accept Foo. – Al Bundy Mar 08 '21 at 17:05
  • @AlBundy "these functions do not look to be generic" they aren't _generic_ because they don't have _generics_ (e.g. `fn new(f: X)`), but what's that have to do with it? If something accepts an `Arc`, then presumably it makes use of the fact that it's an `Arc` somehow. Otherwise it'd take a `&Foo` or a `Foo` or some generic that specifies the minimal API surface area required. – Shepmaster Mar 08 '21 at 17:20
  • Ok, I think I know what is wrong with my reasoning. The function should be rather used as a method associated with the type, when the type is used in different scenarios (Rc, Mutex, Arc etc.). – Al Bundy Mar 08 '21 at 17:37
0

My answer is similar to Shepmaster's answer, but maybe a little more practical since it doesn't consume the argument to foo2 and it gives you a reference to the target instead of taking it out of the container.

I never have any luck implementing traits based on other traits so I didn't try to do that here.

use std::ops::Deref;
use std::rc::Rc;
use std::sync::{Arc, Mutex, MutexGuard};

fn foo(n: i32) {
    println!("n = {}", n)
}

trait Holding<T> {
    type Holder: Deref<Target = T>;
    fn held(self) -> Self::Holder;
}

fn foo2<H: Holding<i32>>(x: H) {
    let h = x.held();
    println!("x = {}", *h);
}

impl<'a, T: 'a> Holding<T> for &'a T {
    type Holder = &'a T;
    fn held(self) -> Self::Holder {
        self
    }
}

impl<'a, T> Holding<T> for &'a Rc<T> {
    type Holder = &'a T;
    fn held(self) -> Self::Holder {
        &**self
    }
}

impl<'a, T> Holding<T> for &'a Mutex<T> {
    type Holder = MutexGuard<'a, T>;
    fn held(self) -> Self::Holder {
        // this can panic
        self.lock().unwrap()
    }
}

impl<'a, T> Holding<T> for &'a Arc<Mutex<T>> {
    type Holder = MutexGuard<'a, T>;
    fn held(self) -> Self::Holder {
        // this can panic
        (*self).lock().unwrap()
    }
}

fn main() {
    let a = 1;
    let b = Rc::new(2);
    let c = Mutex::new(3);
    let d = Arc::new(Mutex::new(4));

    foo(a);
    foo(*b);
    foo(*(c.lock().unwrap()));
    foo(*((*d).lock().unwrap()));

    foo2(&a);
    foo2(&b);
    foo2(&c);
    foo2(&d);
}
Shepmaster
  • 388,571
  • 95
  • 1,107
  • 1,366
NovaDenizen
  • 5,089
  • 14
  • 28