Register a SA Forums Account here!
JOINING THE SA FORUMS WILL REMOVE THIS BIG AD, THE ANNOYING UNDERLINED ADS, AND STUPID INTERSTITIAL ADS!!!

You can: log in, read the tech support FAQ, or request your lost password. This dumb message (and those ads) will appear on every screen until you register! Get rid of this crap by registering your own SA Forums Account and joining roughly 150,000 Goons, for the one-time price of $9.95! We charge money because it costs us money per month for bills, and since we don't believe in showing ads to our users, we try to make the money back through forum registrations.
 
  • Post
  • Reply
VikingofRock
Aug 24, 2008




Helicity posted:

I already had imposter syndrome - I don't need a language to tell me I suck.

Just remember that memory management is hard, and even people who have been writing C for decades get it wrong. Just look at the countless buffer overflow / use after free bugs that have gotten CVEs over years. So rustc isn't telling you that you suck, it's helping you catch the mistakes that everyone makes.

Adbot
ADBOT LOVES YOU

Linear Zoetrope
Nov 28, 2011

A hero must cook
Also remember Rust significantly limits the number of usable memory patterns out of the box, so some perfectly valid and safe constructs are disallowed.

xtal
Jan 9, 2011

by Fluffdaddy
There's one thing I remember from learning Rust: you really need to read and internalize the semantics before you can write a program, wheres some other languages can be learned by trial and error. Even though the error messages are good and getting better, if you are running in to issues with the borrow checker, read more documentation about the borrow checker before you read the error message.

luchadornado
Oct 7, 2004

A boombox is not a toy!

All the above was useful, and what helped me over this hurdle was just walking through the code and describing what it does in plain English, and also easing up on my fear of using the heap. Rust definitely prefers to put things on the stack and for good reason, but you should use the heap when it makes sense.

xtal posted:

There's one thing I remember from learning Rust: you really need to read and internalize the semantics before you can write a program

That's a lesson that's finally starting to stick in my fat head. Related to my hurdle, I actually had to think about what kind of iterator I wanted and how filter() worked under the hood - things I've taken for granted in every other language.

Dominoes
Sep 20, 2007

Hey bros; question on Macros. I've read the 2018 book page on Macros and a few tutorials. I've never seen anything like this in my programming experience; this is beautiful, and completely appropriate for a use case I'm about to describe: I'd like to build a frontend framework using bindgen.

I'd like to make a custom syntax for making HTML elements, where I've defined an element to have attributes, style, a tag type, text, and children, most of which are optional. It looks like you can do a lot with macros; implement JSX/etc without regex and strings; make a whole new sub-language inside rust? There are no rules.

Struct I'd like to make a nice creation syntax for:
Rust code:
pub struct El {
    pub tag: Tag,
    pub children: Vec<El>,
    pub attrs: Attrs,  // Attrs and Style are thinly-wrapped HashMaps
    pub style: Style,
    pub text: Option<String>,
}
With creation syntax something like this:
Rust code:
// The tuples are due to lack of a HashMap literal syntax in rust... which I'm suspicious could also be fixed using macros.
!div[ [("alt", "a div")], [("color", "black"), ("display", "grid")] ]  // attrs, and style only

!h2[ [("alt", "a div")],  "text" ]  // attrs and text, which it could infer from types

!h2[ style=[("color", "black")] ,  "text" ]  // style and text
Should I use a declarative macro, or a procedural one? Are they effectively equivalent, but with the former using regex-like syntax, and the latter using something more akin to a normal function? It seems like I could accomplish this in either.

Dominoes fucked around with this message at 03:46 on Nov 19, 2018

Arcsech
Aug 5, 2008

Dominoes posted:

Should I use a declarative macro, or a procedural one? Are they effectively equivalent, but with the former using regex-like syntax, and the latter using something more akin to a normal function? It seems like I could accomplish this in either.

I don't know much about macros, but I just saw earlier today that someone beat you to this, you might be able to take a look at what they do: https://github.com/bodil/typed-html

It looks like they're using procedural macros? I think? I haven't really touched macros at all, I really should.

Dominoes
Sep 20, 2007

Arcsech posted:

I don't know much about macros, but I just saw earlier today that someone beat you to this, you might be able to take a look at what they do: https://github.com/bodil/typed-html

It looks like they're using procedural macros? I think? I haven't really touched macros at all, I really should.
Thanks! Looks like there are several libs trying what I describe already; pressing anyway! I love JSX... Or thought I did. I loved being able to integrate HTML and styles with code logic, vice in separate templates. Not crazy about the syntax itself; going to try to keep this Rust-like, but with terse syntax.

There's a popular lib called Yew that uses something similar to that, but it uses stdweb, vice bindgen.

Any ideas on how to handle making proc macros a sep crate (as they evidently require) without being codependent with the main crate? It looks like in that and other examples, the proc_macro crate never refs the main crate; I think they're putting all the types that may need to be returned in it... which begs the question of what actually needs to be in the (main / outer) non proc-macro crate.

Dominoes fucked around with this message at 06:00 on Nov 19, 2018

tinaun
Jun 9, 2011

                  tell me...
https://dtolnay.github.io/rust-quiz/

I consider myself pretty experienced with rust, and half this quiz broke me. #2 is evil.

taqueso
Mar 8, 2004


:911:
:wookie: :thermidor: :wookie:
:dehumanize:

:pirate::hf::tinfoil:

I guess I don't know much about macros! I love that the quiz has a nice explanation for each Q.

Workaday Wizard
Oct 23, 2009

by Pragmatica
That quiz kicked my rear end. Good thing I stick to basics.

Dominoes
Sep 20, 2007

Hey dudes. Running into an issue with passing closures around. I've distilled a somewhat-more detailed problem into the basics:
Playground

Rust code:
use std::boxed::Box;

struct Event {
    val: String
}

struct Element {
    events: Vec<Box<FnMut() -> Event>>
}

impl Element {
    fn add(&mut self, handler: impl FnMut() -> Event + 'static) {
        self.events.push(Box::new(handler));
    }
}

// If you comment this out and use the add method above, it works.
fn create(event: Event) -> Element {
    let mut el = Element { events: Vec::new() };
    el.events.push(Box::new(|| event));
    el
}


fn main() {
    // error[E0507]: cannot move out of captured outer variable in an `FnMut` closure
    let event = Event { val: "test".into() };
    let el = create(event);
    
    // let mut el = Element { events: Vec::new() };
    // el.add(|| event);
    
}
 
Essentially, the el.add() method, and the create() function should do the same thing, but the method works while the func doesn't. I'm building an API, and know how I want people to interact with it, so I'd like to get the function Actually a mischievous macro approach working. Any ideas on how to reflow this? For reasons not obvious in this example I don't wish to eschew the event type, but I suspect its existence, and reluctance to allow its contents to be stolen is the problem. If I could politely instruct it to die after the create method is run, the problem would be solved.

Linear Zoetrope
Nov 28, 2011

A hero must cook
I get the same error both ways.

Are you sure you want FnMut and not FnOnce here?

I think the issue you're running into is that FnMut needs to be callable multiple times whereas FnOnce only needs to be called once. You can imagine each having a hidden context or self argument where

FnOnce(self, (other_args)) -> output
FnMut(&mut self, (other_args)) -> output

It sounds like you want the former, since you want it to consume itself.

You could also fix this by cloning from the closure and moving the event into the constructor:

code:
struct Element {
    events: Vec<Box<FnMut() -> Event>>,
}

// If you comment this out and use the add method above, it works.
fn create(event: Event) -> Element {
    let mut el = Element { events: Vec::new() };
    el.events.push(Box::new(move || event.clone()));
    el
}
Which would allow the event to be processed multiple times.

Linear Zoetrope fucked around with this message at 06:10 on Dec 4, 2018

Mr. Glass
May 1, 2009
just to add to what the previous poster said, the reason that your closure is cannot be called multiple times is that it returns `event` by value, which means it needs to be moved (since it doesn't implement `Copy`), and that can only happen once.

i also get the same error for both.

Dominoes
Sep 20, 2007

Thanks a lot! The trick was the clone + move you mentioned.

luchadornado
Oct 7, 2004

A boombox is not a toy!

Trying to wrap my head around lifetimes when trait bounds are in play. I ended up using the following code, which seems to be the idiomatic way of doing what I want to do:

code:
pub struct Fetcher<'a> {
    pub client: &'a Client,
    pub config: &'a Config,
    pub logger: &'a Logger,
}

impl<'a> Fetcher<'a> {
    pub fn new(client: &'a Client, config: &'a Config, logger: &'a Logger) -> Fetcher<'a> {
        Fetcher {client, config, logger}
    }

    pub fn fetch<T: DeserializeOwned>(&self, url: &str) -> Result<T, Box<std::error::Error>> {
        let mut response = self.client.get(url)
            .header(reqwest::header::USER_AGENT, self.config.user_agent.as_str())
            .send()?;

        let mut body = String::new();
        response.read_to_string(&mut body)?;
        let result: T = serde_json::from_str(&body)?;

        Ok(result)
    }
}
But let's say I wanted to use "Deserialize" instead of "DeserializeOwned". I end up giving the Deserialize trait a lifetime of 'a, but then I keep running into "body does not live long enough, borrowed value does not live long enough". Which I understand *why* it's happening, but I can't seem to figure out how to work around it.

Vanadium
Jan 8, 2005

That sounds like it's unlikely to work, I don't think 'a is the right lifetime here. You need to arrange for the body to stick around for the lifetime parameter to Deserialize, so if that should be 'a then you'd probably need to stash the body in the Fetcher struct, which sounds like somewhat wonky semantics for a Fetcher.

Maybe it works if you ask the caller for a &mut String to read into and use the lifetime of that for Deserialize?

Disclaimer: I've only used serde with some extremely mechanical Serialize/Deserialize implementations, I might be missing out on the subtleties.

Vanadium
Jan 8, 2005

https://play.rust-lang.org/?version=nightly&mode=debug&edition=2015&gist=a31af09574565b12582fdd6477e32d44 seems to work modulo playground sandboxing:

code:
    pub fn fetch2<'b, T: Deserialize<'b>>(&self, url: &str, body: &'b mut String) -> Result<T, Box<std::error::Error>> {...}
...
    let mut b = String::new();
    f.fetch2(..., &mut b).unwrap();
But to me it sounds like DeserializeOwned makes sense as the bound here.

I got a bit upset that I couldn't figure out how to avoid needing this weird DeserializeOwned trait that I'd never heard of before, but I realized it's implemented for for<'t> Deserialize<'t> and you can basically inline that into your definition if you wanted:

code:
    pub fn fetch<T: for<'t> Deserialize<'t>>(&self, url: &str) -> Result<T, Box<std::error::Error>> {

luchadornado
Oct 7, 2004

A boombox is not a toy!

Vanadium posted:

I got a bit upset that I couldn't figure out how to avoid needing this weird DeserializeOwned trait that I'd never heard of before, but I realized it's implemented for for<'t> Deserialize<'t> and you can basically inline that into your definition if you wanted:

code:
    pub fn fetch<T: for<'t> Deserialize<'t>>(&self, url: &str) -> Result<T, Box<std::error::Error>> {

That's exactly how I felt too! Thanks for figuring it out.

Dominoes
Sep 20, 2007

Looking for critique and suggestions for a web framework I'm working on. Repo. Mainly regarding the guide (Readme) and API. My goal is for this to be accessible to web programmers who are not familiar with Rust. Motivation was the messy JS tooling and ecosystem, and the poor documentation of existing Rust/WASM frameworks.

I hope to publish it on crates.io within the next few weeks. I have an API I like, and a basic virtual DOM is almost ready with plenty of room for optimization. I have a good deal of work to do on cleaning up how events trigger; struggling on that one. Simple clicks etc work, but anything involving accessing event parameters (like text input) is a WIP. Also need to either integrate, or document wasm-bindgen's fetch API, and how you'd use it with Serde to communicate with a server. And a router.

Dominoes fucked around with this message at 07:32 on Dec 9, 2018

Chopstick Dystopia
Jun 16, 2010


lowest high and highest low loser of: WEED WEE
k
Is there a good reason that the rand crate uses gen_range(lower_inclusive, higher_exclusive) instead of being consistently inclusive or exclusive on both range arguments?

My basic assumption is something to do with ease of use of .len() for indexing as the higher argument but I'm curious.

v - of course, thanks, I should probably stop programming while I have a headcold

Chopstick Dystopia fucked around with this message at 09:23 on Dec 9, 2018

taqueso
Mar 8, 2004


:911:
:wookie: :thermidor: :wookie:
:dehumanize:

:pirate::hf::tinfoil:

It matches normal ranges. (..)

spiritual bypass
Feb 19, 2008

Grimey Drawer

Dominoes posted:

Looking for critique and suggestions for a web framework I'm working on. Repo. Mainly regarding the guide (Readme) and API. My goal is for this to be accessible to web programmers who are not familiar with Rust. Motivation was the messy JS tooling and ecosystem, and the poor documentation of existing Rust/WASM frameworks.

I hope to publish it on crates.io within the next few weeks. I have an API I like, and a basic virtual DOM is almost ready with plenty of room for optimization. I have a good deal of work to do on cleaning up how events trigger; struggling on that one. Simple clicks etc work, but anything involving accessing event parameters (like text input) is a WIP. Also need to either integrate, or document wasm-bindgen's fetch API, and how you'd use it with Serde to communicate with a server. And a router.

I look forward to giving this a shot. I've been really frustrated with how the Yew project can't be bothered to write any useful docs and Elm just feels so tedious sometimes...

Dominoes
Sep 20, 2007

rt4 posted:

I look forward to giving this a shot. I've been really frustrated with how the Yew project can't be bothered to write any useful docs and Elm just feels so tedious sometimes...
What in particular to you find tedious about Elm? I'd like to avoid that.

crazypenguin
Mar 9, 2005
nothing witty here, move along

Dominoes posted:

Looking for critique and suggestions for a web framework I'm working on. Repo. Mainly regarding the guide (Readme) and API.

Is there a reason you're not having people use wasm-pack?

Dominoes
Sep 20, 2007

crazypenguin posted:

Is there a reason you're not having people use wasm-pack?
I couldn't get its --no-modules feature working, and, at least on my system, it strips compiler errors of their colors, and it dumps extra files into the pkg directory. I'll probably switch once sorted - hopefully it will preclude the need for the custom build script.

For the first issue, the command is apparently wasm-pack build --target nomodules, but I can't get it working.

Of note: Chat about no-modules

edit: Attempted again now: running into an error about failing to read Cargo.toml.
edit2: I should probably just submit issues/PRs instead of waiting; the maintainers seem like the type who'd appreciate it.

Dominoes fucked around with this message at 19:21 on Dec 9, 2018

spiritual bypass
Feb 19, 2008

Grimey Drawer

Dominoes posted:

What in particular to you find tedious about Elm? I'd like to avoid that.

It feels unnecessarily difficult every time I need to cause more than one side effect. It still seems like the best thing available, but a not-strictly-functional alternative is welcome in my world.

Dominoes
Sep 20, 2007

I appreciate how in languages like Python and Rust, you can choose which functional concepts when you'd like (Often directly inspired by languages like Haskell), without the associated restrictions.

luchadornado
Oct 7, 2004

A boombox is not a toy!

It's getting hard to find languages that don't incorporate some of the basics because FP does a lot of really cool things that make dev's lives easier. I'm looking at you, Golang. I really don't want to work in languages without sum types these days.

Dominoes
Sep 20, 2007

Clippy is really, really good! My experience with linters:

TSlint/Jslint, as supplied by Create-React-App: Mostly subjective style enforcement that makes the app a pain to transpile; ended up turning off most lints, then abandoning the tool.

Clippy: Makes consistently good recommendations to make code more readable, avoid traps, and teach about language features.

luchadornado
Oct 7, 2004

A boombox is not a toy!

Agreed - linters that have opinions about styles and no fmt functionality are only slightly less annoying than arguments with people about number of spaces in a tab.

Also, this looks cool: https://github.com/withoutboats/romio

Dominoes
Sep 20, 2007

Hey dudes, published an initial v of my frontend-framework crate. Any suggestions/critique on API/docs etc would be appreciated. Or just making sure that you can get a hello-world working using the quickstart info.

There's a nasty bug lurking somewhere in the vdom.
edit: nasty bug fixed.

Dominoes fucked around with this message at 00:24 on Dec 15, 2018

repiv
Aug 13, 2009

The wasm32-unknown-unknown target is available on stable Rust now, so there's no need for the setup process to involve installing nightly anymore :)

(unless you're using other nightly features)

Dominoes
Sep 20, 2007

Good call. Updated repo and quickstart.

DONT THREAD ON ME
Oct 1, 2002

by Nyc_Tattoo
Floss Finder
I'm implementing Raft (for learning) and I'm using async/await futures 0.3 in compatibility with tokio 0.1.

It's still a pain in the rear end (mostly due to issues with the compat shims at this point) but async/await is way nicer to work with than chaining 0.1 futures. I'm actually getting stuff done instead of spending hours trying to understand why my types aren't lining up. It's going to be good eventually.

tinaun
Jun 9, 2011

                  tell me...
yeah i've been playing around with tide (yet another async web framework) and writing my own futures 0.3 libs for timers and websockets and stuff it's very nice.

America Inc.
Nov 22, 2013

I plan to live forever, of course, but barring that I'd settle for a couple thousand years. Even 500 would be pretty nice.
Is it a recommended pattern in Rust to group together all of the trait implementations, across all structs that use the trait, in one file separate from the files the rest of the struct methods are implemented in? At least within a package?

I'm learning Rust, so that question may sound incoherent. Here's another way of asking.

Here is this basic program:
code:
extern crate nix;

use nix::sys::socket::{self, sockopt::ReuseAddr};
use std::{error::Error, net::TcpListener, os::unix::io::AsRawFd};

fn main() -> Result<(), Box<dyn Error>> {
    let listener = TcpListener::bind("127.0.0.1:7878")?;

    socket::setsockopt(listener.as_raw_fd(), ReuseAddr, &true)?;

    Ok(())
}
Notice that I call listener.as_raw_fd() which is an implementation of the trait AsRawFd. To call this method, I have to import the trait and not just the class that implements the trait, TcpListener.

This is a bit strange to me, because in C++, C#, Java, Typescript etc. pick an OOP language, a class will implement either an abstract class or interface, and the client code will only need to include the class that implements the interface. It doesn't need to directly include the interface (although it may be implicitly included in the header).
Of course, Rust doesn't have a concept of include and implementation files - the declaration and implementation is done together with impl. You can have separate files declaring and defining methods for the same struct, which is what happens with TcpListener. Looking further at the source code for tcp.rs and net.rs, I can see that TcpListener::as_raw_fd() is defined in net.rs separately from the rest of the methods for TcpListener in tcp.rs. In fact, all of the different implementations of AsRawFd for each struct in the std library are defined in net.rs separate from the main struct implementations.

Technically, this is something that could be done in other OOP languages, for example in C++ you could split your implementation. But it's not usually done that way. Hence we come back to the same question I started this post with.

Of course, this pattern would only work for the implementations within your own package, because obviously if you're exporting a trait the 3rd party developers will implement the trait in their own packages.

America Inc. fucked around with this message at 09:58 on Apr 7, 2019

Ralith
Jan 12, 2011

I see a ship in the harbor
I can and shall obey
But if it wasn't for your misfortune
I'd be a heavenly person today
Usually makes sense to have implementations next to their structs. Note that rust does not have anything called a "class," and does not seek to imitate OOP patterns for their own sake.

Traits must be imported for their methods to be used because any crate can define and implement a trait for any type, which would otherwise allow arbitrarily distant transitive dependencies to introduce ambiguities in completely unrelated parts of your code.

Sagacity
May 2, 2003
Hopefully my epitaph will be funnier than my custom title.
If you're looking for analogues with other languages perhaps it's useful to think of these implementations as extension methods you may have seen in C# or Kotlin. They augment an existing class but they also require both the class and the extension methods to be imported separately.

Linear Zoetrope
Nov 28, 2011

A hero must cook
Some of this has resulted in the prelude pattern. For instance, it's pretty common now to do use std::io::prelude::* which is just a submodule the publicly reexports all the common IO traits, and a few libraries that are really trait-heavy have adopted this as well.

Adbot
ADBOT LOVES YOU

Beamed
Nov 26, 2010

Then you have a responsibility that no man has ever faced. You have your fear which could become reality, and you have Godzilla, which is reality.


Linear Zoetrope posted:

Some of this has resulted in the prelude pattern. For instance, it's pretty common now to do use std::io::prelude::* which is just a submodule the publicly reexports all the common IO traits, and a few libraries that are really trait-heavy have adopted this as well.

When I was first learning Rust, this pattern made it impossible to learn the loving language. Knowing later on that it just re-exports poo poo was helpful, but being unable to compile code, despite thinking you have the right imports, until magically the prelude module has some special macro for some special trait that lets it compile, was really the worst.

  • 1
  • 2
  • 3
  • 4
  • 5
  • Post
  • Reply