These are chat archives for rust-lang/rust

10th
Mar 2019
kylegoetz
@kylegoetz
Mar 10 01:45
@WreckedAvent I guess where I'm coming from is that if main depends on A, and A depends on B, having to import B inside main tightly couples code (at least to me) unnecessarily. The terminology confuses me, I guess. To me, B can't really be called a "module" if something that doesn't even use it (main) still has to import it just so A (which main does use) can use B. I'm definitely dumber than the people who created Rust and who define the language, but it seems like a compiler ought to look at which files reference which other files and create a dependency tree rather than having to have main list all the dependencies (which is how I interpret the case of main having to "import" B even though it never directly uses it. I understand Rust does things differently, and at this point I'm more responding to people trying to explain it to me further like it should make more sense to me but it makes less sense to me. Not your fault of course, it's Rust + the languages I'm coming from that are biasing me to see things a certain way!
I suppose the way around this is that I should have B and A be a whole different library separate from main, and then main relies on this "third party" library of A and doesn't have to import B.
Riley Cat
@WreckedAvent
Mar 10 01:55

given:

src/
--lib.rs
--a.rs
--a/
----b.rs
----b/
------c.rs

then lib can just mod a, a can just mod b, and b can just mod c, and it'll all compile without lib having any clue about b or c

but

if you have this:

src/
--lib.rs
--a.rs
--b.rs
--c.rs

you have a simple enough project that you can just import them all in lib and not worry about it

I believe this is enabled since #include and variants are usually just line noise
so in the simple case you just have one place that does all of your importing and job's done for the entire project
Riley Cat
@WreckedAvent
Mar 10 02:01
but I have no way to know the motivation behind it
kylegoetz
@kylegoetz
Mar 10 02:02
OK so I would like that first structure, except that in reality, I have a trait C. A and B implement the trait. main uses A and B. So with structure #1, I would need to have a copy of trait C in both an "a" folder and a "b" folder.

I'm thinking about it like this (using potentially non-Rust terms): I have an Image class and a Video class. They're both ScannableFiles. ScannableFile has a function is_valid. Image and Video both implement ScannableFile. main walks through a directory and looks at file extension and throws the file away or constructs an Image or constructs a Video. There's DB stuff involved, image processing, etc. So the project could become complex enough where the second version is suboptimal, but the first version would require either actual copies or a symlink such that I'd have both image/scannable_file and video/scannable_file.

Just feels like the way I'm approaching this is very unidiomatic for Rust, because copies of code shouldn't be happening.

Riley Cat
@WreckedAvent
Mar 10 02:08

given:
src/
--lib.rs
--traits.rs
--a.rs
--a/ {... the same ...}

if lib mods in both a and traits, then all the other files can provide implementations for the traits in traits.rs

but you will need to namespace them so like
impl crate::traits::MyFancyTrait for usize {

}
(unless again you use use)
kylegoetz
@kylegoetz
Mar 10 02:09
So I should move ScannableFile, Image, and Video to a separate library project and then have main in its own non-lib project and that's the idiomatic Rust file structure for this?
I don't know what time it is for you, but maybe I should just leave this be for now and implement my project a dumb way just to get it working, then work my way through the book more until I understand this stuff rather than wasting your time.
Riley Cat
@WreckedAvent
Mar 10 02:10
the point is, so long as the trait is included, somewhere, somehow, you can implement it if you know the path to it
you don't need copies
kylegoetz
@kylegoetz
Mar 10 02:11
OK I think I got it. I can give it a whirl and see.
Thank you. I appreciate it.
Riley Cat
@WreckedAvent
Mar 10 02:11
:+1:
kylegoetz
@kylegoetz
Mar 10 02:12
I've expected this kind of thing to come up, having heard about how Rust makes you think about things differently than other languages. The own/borrow is very novel for me. I learned C++ when I was like 12, which was over two decades ago, and I think that was the last time anything even remotely like pass by reference/value showed up explicitly (even though I'm aware this isn't really pass by ref/val, just appears that way superficially to me as a noob)
Riley Cat
@WreckedAvent
Mar 10 02:13
I think rust is a wonderful way to start thinking about these things
the compiler warnings are a little wonky sometimes but rust will complain pretty fast and make things really hard if you're doing things the "wrong" way
so it sort of nudges you more into writing idiomatic code since it's the path of least resistance
like here, why have a bunch of files? easier to just make one file with your trait and its implementation
rust makes it pretty easy to just have a few large files and as such that's what you tend to see
whereas in a language like c# or java they take care of handling file imports and such for you so it's very easy to have millions of incredibly small files
meaning you need to go through like 12 files just to see how a simple app works
kylegoetz
@kylegoetz
Mar 10 02:36
To me, large files make it really hard to code because you're constantly scrolling up and down and up and down a whole lot. And then even the Rust manual says to put your unit tests in the same file as the things you're testing, so that's even more code you're ignoring 99% of the time.
And then if you're implementing a function in a trait and have two implementations, you have the same function name three times in the file, so scrolling up and down you can easily start looking at the wrong one without realizing it.
Riley Cat
@WreckedAvent
Mar 10 02:38
it's just a different way of thinking
I personally like the documentation tests since there's literally no distance from the code
you see definition, the use cases, and the implementation all in one tight area
kylegoetz
@kylegoetz
Mar 10 02:40
Yeah document tests are something further on in the book that I haven't gotten to. I'd assumed it was code that generated documentation, but given what you've just said, it sounds like it's similar to a Python-style of writing tests that are also documentation or something.
like comments that contain tests, but I'll see it when I get to it in the book :D
Sam Johnson
@sam0x17
Mar 10 02:41
once you navigate a file with CTRL+F, it becomes easier the more that is in that file
kylegoetz
@kylegoetz
Mar 10 02:42
I guess it seems like if you had one file with one module, ctrl+f would be easier, not harder. Are you modifying the module? Then be in that file. Otherwise, close that file and open the one you're working on. But thats' my way of thinking.
But that's the benefit of different types of coding: it keeps your brain strong by having to learn different approaches to the same goal.
Rust will work different parts of my brain than, say, Scala or TypeScript.
Riley Cat
@WreckedAvent
Mar 10 02:43
/// Represents a musical accidental (sharps, flats, etc.)
pub enum Accidental {
  DoubleFlat = -2,
  Flat = -1,
  Natural = 0,
  Sharp = 1,
  DoubleSharp = 2,
}

impl Accidental {
  /// Tries to converts from a single `char` to an `Accidental`. Unicode or ascii representations accepted.
  ///
  /// ## Examples
  ///
  /// ``
  ///# use bach::notes::Accidental::{self, *};
  ///
  /// assert_eq!(Sharp, Accidental::from_char('♯').unwrap());
  /// assert_eq!(Flat, Accidental::from_char('b').unwrap());
  /// assert_eq!(DoubleFlat, Accidental::from_char('𝄫').unwrap());
  /// ``
  pub fn from_char(c: char) -> Result<Accidental, NoteParseError> {
    match c {
      '#' | '♯' => Ok(Accidental::Sharp),
      'b' | '♭' => Ok(Accidental::Flat),
      '𝄫'      => Ok(Accidental::DoubleFlat),
      '𝄪'       => Ok(Accidental::DoubleSharp),
      '♮'       => Ok(Accidental::Natural),
      _         => Err(NoteParseError::InvalidAccidental),
    }
  }
 ...
}
Sam Johnson
@sam0x17
Mar 10 02:43
in some languages, it's super hard to find where side effects are coming from (e.g. Ruby) -- there are lots of things I don't like about Rust, but the tendency towards large files is great imo
Riley Cat
@WreckedAvent
Mar 10 02:43
random whack of code
but you see there's assertions right above the function
and if you hover over it in vscode or something you get a pretty markdown view
kylegoetz
@kylegoetz
Mar 10 02:45
Yup, that's what your comment made me think it was. :) Very cool bit of code. I didn't know that about VS Code (which is what I use these days). I look forward to adding doc tests for that.
Riley Cat
@WreckedAvent
Mar 10 02:46
image.png
what's better than having literal snippets of code telling me how to use the function when I hover over it? it's pretty nice
Riley Cat
@WreckedAvent
Mar 10 02:54
image.png
(it also generates a neat website)
Sam Johnson
@sam0x17
Mar 10 05:17
@tanriol how did you do this pub targets: Vec<Box<dyn BytesSink>> without incurring an error like "the trait BytesSync cannot be made into an object" ?
Sam Johnson
@sam0x17
Mar 10 06:12
aha, my problem is I have another method that uses Self, so the struct is not object safe, fml
Farzeen
@happycoder97
Mar 10 06:37
What does @param mean in macros?
Denis Lisov
@tanriol
Mar 10 08:09
@happycoder97 Nothing special from the Rust point of view. Macro writers sometimes insert things like this to choose the correct branch when expanding macros recursively.
Farzeen
@happycoder97
Mar 10 08:44
Got it. Thanks!
Sam Johnson
@sam0x17
Mar 10 08:54
@tanriol thanks again for your help -- I was able to rework the ideas from your code into something that exactly conforms to my original spec -- the only awkwardness, which is really fine, is that I end up having a limit_callback closure that does not receive a self parameter (so it cannot mutate the calling stream), and an fn on_limit(&mut self); method that must also be implemented, that lets you make any changes one needs to make to the stream when the limit fires. The limit_callback and on_limit methods are called one after the other. The reason for this is if I use the signature you used for the limit callback (which includes FnMut(Self)) the trait is no longer object safe, which amusingly makes it impossible for me to define my list of targets because of compiler reasons:
https://gist.github.com/sam0x17/53c932c89c594c1ae9d9c129fadd69c3
your code got around that issue by having multiple traits, but I was curious if I could stubbornly get my original interface to work and it looks like the answer is 99% yes
Sam Johnson
@sam0x17
Mar 10 08:59
I also was able to support both a targets list, and a data_callback, which makes testing much easier
Denis Lisov
@tanriol
Mar 10 09:03
@sam0x17 Really, no comments. I've intentionally tried to rewrite your code in more or Rust-native style. Now you mixed everything back again, including carefully factored out limiting functionality. Why? I don't understand the motivation so won't make any real comments.
Also, you've dropped error handling (what are you going to do when your TCP connection goes down - panic and crash the app?) and returned to fn transform<'a>(&self, data: &'a [u8]) -> &'a [u8]; (which, I assure you, will not work for any kind of encryption/decryption/compression/decompression).
Denis Lisov
@tanriol
Mar 10 09:18
(and yes, the limit callback could take &mut dyn Stream if that's enough)
Sam Johnson
@sam0x17
Mar 10 09:39
@tanriol again I really appreciate what you've done and will continue to return to it as a reference (and may use it in place of my code if I someday understand all of it). I plan to add error handling much the same way you did once I get to that step but I first need to figure out how I actually want that to bubble up -- I am probably going to have to have an interface on each stream type that handles error situations differently, e.g. retry in certain network situations, perform some reverting or clean-up to prevent data loss, and I haven't even begun to wrap my head around that yet at least in the rust environment. I did try &mut dyn Stream at great lengths but this always led to borrowing violations that prevented me from legally calling the callback from within the write method. My main hang-up is that I fundamentally don't grasp some of the constructions you used, for example I don't understand how you got the Print struct to have no data members, but still manage the object state needed for streaming, and there was enough stuff in the byte operations that you were doing that I didn't understand it and could no longer be sure that it uses slices wherever possible and avoids data duplication and copying wherever possible (it may do all of that, I just don't understand enough of it to verify). But yeah do know I really really appreciate what you've done and I am still studying your code and will likely be replicating a lot more of it as I come to understand the various parts.
Sam Johnson
@sam0x17
Mar 10 09:51
you know what, you are right, yours is a lot, lot better, I just freaked out when I couldn't find the structure of the write method in there -- the external API is really good though -- I'll just try harder to understand it -- I've been in a 14 hour fever dream trying to get my original interface because I'm super OCD about that
yea now I see the Print struct for example is just an empty shell for the transform / write methods, and that is an object that gets loaded by the limiter .... gotcha
Sam Johnson
@sam0x17
Mar 10 09:58
yes, now that I've struggled with this for 14 hours, looking back at yours, it can do way more than mine lol can't believe you did that all in like 20 mins
and yeah, looks like the limiting all works correctly too :+1:
Sam Johnson
@sam0x17
Mar 10 10:28
update: haha yeah so not only am I using it but I took everything on handle() and cleaned it up so there are methods on the limiter that delegate to handler, so you can just do pipeline.set_limit and pipeline.reset_stats etc directoy
verilog15
@verilog15
Mar 10 22:24
Hi guys! I saw that rust support transitivity: coercion "T_1 to T_3 where T_1 coerces to T_2 and T_2 coerces to T_3" (from docs). Could someone show a basic examples which proves it?