Strings in Rust FINALLY EXPLAINED!

Поделиться
HTML-код
  • Опубликовано: 7 июн 2024
  • The ultimate Rust lang tutorial. Follow along as we go through strings in Rust. We will be talking about UTF-8, the &str and String types, indexing into strings, and more!
    📝 Get notified when the Rust Cheatsheet comes out: letsgetrusty.com/cheatsheet
    The Rust book: doc.rust-lang.org/stable/book/
    Chapters:
    0:00​ Intro
    1:09 What is a string?!
    6:53 &str and String
    10:21 Creating strings
    12:19 Manipulating strings
    13:31 Concatenating strings
    15:29 Indexing into a string
    19:44 Strings and functions
    20:32 Outro
    #letsgetrusty​​ #rust​lang​ #tutorial
  • НаукаНаука

Комментарии • 198

  • @letsgetrusty
    @letsgetrusty  2 года назад +6

    📝 Get your *FREE Rust cheat sheet* : www.letsgetrusty.com/cheatsheet

  • @antonioquintero-felizzola5334
    @antonioquintero-felizzola5334 2 года назад +59

    This has become my favorite RUST channel on RUclips.

    • @stardustbiscuits
      @stardustbiscuits 2 года назад +8

      Because this is the only rust channel on RUclips

  • @Yotanido
    @Yotanido 2 года назад +37

    Little note about function parameters:
    Taking &str instead of String is good, but only if you don't need an owned String. If you do need an owned String, make sure to take a String, so the caller can decide how the owned string is generated. (For example, the caller might already have a String. If you take a &str, you need an unnecessary clone)
    Now to contradict myself: If you do need an owned String, it might be best to use an impl Into instead. This way, the caller can pass in a &str as well. Improves ergonomics.
    In the same vein, taking impl AsRef instead of &str also allows your function to take an owned string. It is trivial to put an & in front, so it's not as important as Into, but it also slightly improves ergonomics.
    Depending on what you actually do with it, you might even want your string input to be IntoIterator or something. This does decrease ergonomics, since the caller now needs to call chars on the string, but it does mean your function will also work with, for example, Vec.
    If you sometimes return an owned string and sometimes a &str, you can use Cow. For example, if you sometimes return a string literal, Cow

  • @jonathanmoore5619
    @jonathanmoore5619 2 года назад +62

    I think this is probably your best video yet. It's great that you've gone a little bit deeper. Programmers really need to know this stuff. Thanks for your effort.

  • @JaycenGiga
    @JaycenGiga 2 года назад +49

    At 20:50 you talk about fixed-length encoding using four bytes. This is what UTF-32 does. AFAIK none of the major languages uses it, but Python has an interesting take on it: When a string is created, the interpreter chooses the “best fit“ between ASCII, UTF-16, and UTF-32, so that constant indexing is always possible but not too much memory is wasted. This of course only works because Python strings are immutable.

    • @dacid44
      @dacid44 Год назад +3

      Rust's char type actually is UTF-32, as far as I understand it. Which means you could get very similar functionality to a fixed character length encoding by simply using a Vec (or an &[char] to emulate a string slice), if you so wished. Granted, not all the operations that are possible for the normal string types are implemented for a Vec, but given the size of the Rust ecosystem, there's probably a library to make it possible.

    • @haniyasu8236
      @haniyasu8236 Год назад +1

      I'm like 90% sure that Java `String`s are UTF-32. From my recollection, the `char` type in Java is pretty explicitly an `int` under the hood as well.

    • @abcxyz5806
      @abcxyz5806 Год назад

      ​@Haniyasu char is UTF-16. But if i remember correctly, multibyte encoding does not work in Java. Could be changed now, haven't use Java since Java 8

  • @upgradeplans777
    @upgradeplans777 2 года назад +8

    As a note on your comment at the end: The type that you said that Rust does not have would be represented by Vec in Rust applications. It is not equivalent to rune slices in Go ([]rune), but intended for the same usage.
    In general, go slices are similar to rust vectors. However, there is a difference between char and rune: In Go, rune is an alias for int32. In Rust, char is its own type. With Rust's emphasis on memory safety, safe Rust code cannot generate invalid chars. This means that it's behavior is different from u32, the type that it would otherwise be equivalent with. In Go, it is perfectly possible to create meaningless runes. The same is true for strings by the way, safe Rust code cannot generate invalid UTF-8 values, but no such limitation exists in Go.
    As I understand it, these are equivalent types between Go and Rust:
    - string -> &[u8]
    - rune -> i32
    - byte -> u8
    - []byte -> Vec
    - []rune -> Vec
    - [7]byte -> [u8; 7]
    - [7]rune -> [i32; 7]
    The other Rust types that we mentioned (char, &str, String, Vec, etc) have additional memory safety guarantees that Go does not provide.

  • @billhurt3644
    @billhurt3644 Год назад +1

    This might be the best string and UTF8 encoding video I’ve ever seen. So many experienced, professional programmers really do not understand how Strings actually work, even in their own language or choice. And it truly did demystify for me Rusts behavior around the different string types. Much appreciated.

  • @cramhead
    @cramhead 2 года назад +2

    Liked the little explanation of UTF-8 encoding. Thanks for making the videos. It helpful to refresh what I’ve read and get a few tips too

  • @0xedb
    @0xedb 2 года назад +1

    This earns you the title of professor. Hardly seen anything better explained than this!

  • @nofaldiatmam8905
    @nofaldiatmam8905 8 месяцев назад

    learn more about utf, bytes and string on 20 min video than my 4 year of uni, thanks man ✌️

  • @abhishekdas829
    @abhishekdas829 2 года назад

    Probably the best explanation of string, utf-8, ascii types I’ve encountered in my 15 year career! Keep up the good work!

  • @NOPerative
    @NOPerative Год назад

    Def one of the best, light weight (and logic dense) videos I've seen regarding rust string types from a practical standpoint (concerning aspiring Rustaceans).
    Excellent vid.

  • @mistakenmeme
    @mistakenmeme 2 года назад +46

    Keep it up!! As Rust grows, you will one day be remembered as one of the O.G. Rust youtubers!

  • @WizardOfArc
    @WizardOfArc 2 года назад +2

    I learned something new about how UTF-8 works! Thank you!

  • @dabzilla05
    @dabzilla05 2 года назад +14

    This was driving me crazy last week.
    Really glad to see good, thorough showcase of this concept in Rust.
    Appreciate your content! Keep at it, this will be big when Rust blows up.

  • @rebelmachine88
    @rebelmachine88 2 года назад +1

    The super in-depth content I crave! Great video

  • @danielhadad4911
    @danielhadad4911 2 года назад +1

    Your channel is so cool, thanks for putting the effort in making these tutorials.

  • @chanhhua7050
    @chanhhua7050 2 года назад +2

    Wow, this is extremely useful! I come from the Java world and it was really confuse me when working with Rust string, especially when I need to deal with ideographic characters. Thank you!

  • @jamesbond_007
    @jamesbond_007 Год назад

    Finally! A great explanation of what's going on between string slices and Strings -- thank you! I also appreciated your delving into unicode encoding -- I was worried you were going to rabbit hole on binary representations of a bit characters, but you did exactly the right thing in terms of explaining how unicode encoding works, how it solves the "where am I" when you have a pointer to an arbitrary byte in a unicode string (i.e. "am I at the start?" "where is the next char boundary?") -- I love that you explicitly mentioned that the first byte of a multicode byte string is differentiable based on the high order bits, and that it encodes the length of the multibyte sequence. You mentioned indexing into a Unicode string was a linear operation, which is true, but it's sub-linear in terms of number of bytes explicitly traversed -- if you have 4 4-byte unicode chars in a string, traversal takes only 4 operations, not 16, due to this clever encoding of the first byte.

  • @carlesxaviermunyozbaldo4782
    @carlesxaviermunyozbaldo4782 2 года назад

    Great explanation of this important topic in Rust. Thank you very much!

  • @DavidTorralbaGoitia
    @DavidTorralbaGoitia Год назад

    Amazing content for a beginner! Super helpful! Thank you very much! 🙌

  • @hermannpaschulke1583
    @hermannpaschulke1583 2 года назад +16

    Most people say strings in rust are complicated. For me, it makes a lot of sense how it's handled. I do quite a bit of C progamming on µCs, and there everything is char*

    • @sconosciutosconosciuto2196
      @sconosciutosconosciuto2196 2 года назад +2

      Is rust bad for microcontrollers?

    • @saadisave
      @saadisave 2 года назад +2

      @@sconosciutosconosciuto2196 Embedded Rust usually doesn't have the full standard library. It has parts of std called core and alloc.
      If your resources are limited, you can use a CString, which is null terminated and equivalent to char*.

  • @noblenetdk
    @noblenetdk Год назад

    splendid and thoroughly explained. Bravo!

  • @GolangDojo
    @GolangDojo 2 года назад +8

    Shit just got serious

  • @johnyepthomi892
    @johnyepthomi892 Год назад

    Namaste brother. You’re videos are too good. Keep this format going, tackling each topic standalone or mixed if it’s contextually relevant.

  • @JeremyChone
    @JeremyChone 2 года назад +1

    Very nice in depth video.

  • @malharvora1281
    @malharvora1281 2 года назад

    Nice and useful intro to Rust strings. Thanks :)

  • @muffledcry
    @muffledcry Год назад

    This is a great guide to a subject that has given me a ton of grief in Rust. Awesome job!

  • @Mehraj_IITKGP
    @Mehraj_IITKGP 11 месяцев назад +1

    Here a brief summary of UTF-8 encoding:
    - UTF-8 (Unicode Transformation Format, 8-bit) is an encoding scheme, just like ASCII, for representing Unicode characters.
    - In UTF-8, ASCII characters are represented using a single byte, which means that any valid ASCII text is also valid UTF-8 text.
    - Therefore, UTF8 is backward compatible with ASCII.
    - In UTF-8, characters that can be represented using a single byte (i.e., ASCII characters) are represented as themselves.
    - Characters that require more than one byte are encoded using a combination of multiple bytes.
    - A code point refers to a numerical value assigned to each character or symbol in the Unicode standard.
    - Code points are represented using hexadecimal notation and are typically prefixed with "U+" to distinguish them from other numerical values.
    - For example, character "é" (Latin Small Letter E with Acute) consists of two Unicode code points: the base character "e" (U+0065) and the combining acute accent (U+0301). When encoded in UTF-8, "é" is represented by the bytes 0xC3 0xA9.
    - A grapheme refers to a visual unit of a written language. It represents a single user-perceived character or a combination of characters that are displayed together.
    - len() function returns the number of bytes, not the number of characters in a Unicode-unaware string.
    - len() function returns the number of characters in case of a Unicode-aware string.

  • @skyeplus
    @skyeplus 2 года назад +7

    The thing I like about Rust is you can take a buffer out of one type and transfer it to another. Like for instance you can convert between String, Vec and Box, while keeping the same underlying buffer without reallocation and copying. Hope C++ would have this. It had node transfer in limited form for lists and in newer standard for maps.

    • @SolomonUcko
      @SolomonUcko Год назад +1

      Note that converting to a `Box` reallocates if the length is less than the capacity, but other than that, owned conversions just transfer ownership of the existing allocation.

    • @skyeplus
      @skyeplus Год назад +1

      @@SolomonUcko oh, OK. Good to know. I'm still at the very beginning at learning Rust.

  • @RufusROFLpunch
    @RufusROFLpunch 2 года назад +2

    This was great. I wish I had this when I was first learning Rust.

  • @jeremietamburini
    @jeremietamburini Год назад

    Fantastic explanation, thank you very much! 👍

  • @exhaustedrose
    @exhaustedrose Год назад

    Thanks for this refresher, I was getting pretty rusty.

  • @alexeyermolaev6002
    @alexeyermolaev6002 Год назад

    Awesome content, thanks for your work!

  • @davidtremaine8076
    @davidtremaine8076 Год назад

    You have such great videos. Learning a lot.

  • @primingdotdev
    @primingdotdev 2 года назад +3

    awesome video, just a small thing. Newer programmers may be confused by the lookup vs search times for a character. For UTF-8 (or any variable length encoding) if you want to lookup the nth scalar or grapheme you need to do a linear walk through of the string to count off every time you get to the end of a sequence of bytes representing a scalar/grapheme but for a fixed length encoding (runes, UTF-32) you can rely on each unit (scalar/char/grapheme) being a fixed size and you can just skip (n - 1) * 4 bytes (UTF-32 uses 4 bytes per scalar) to land in the right place. Not sure if I just confused a bunch of people or added clarity for some.

    • @SolomonUcko
      @SolomonUcko Год назад +1

      UTF-32 has fixed length code points/units, but not grapheme clusters

  • @Pilosofia
    @Pilosofia Год назад

    you are the best one to explain the difference between the two.

  • @__abhish
    @__abhish 2 года назад

    really nice content man. Keep it rusty xD

  • @dieust
    @dieust Год назад

    Really helpful, thanks !

  • @rustlabs7932
    @rustlabs7932 2 года назад

    Simply brilliant !!!

  • @thesuperyou2829
    @thesuperyou2829 2 года назад

    what an effort man.... hats off

  • @JakobKenda
    @JakobKenda Год назад

    2:04 you could create an array of chars or Vec since chars are 4 bytes long.

  • @johnandrews5414
    @johnandrews5414 2 года назад

    Outstanding video.

  • @prueba875
    @prueba875 Год назад

    Very interesting video thanks!

  • @dreastonbikrain1896
    @dreastonbikrain1896 Год назад

    Thank you, you demystified Unicode for me, now I see how I would implement some of the UnicodeSegmentation crate myself :)

  • @mumk
    @mumk Год назад

    FINALLY, thanks bro

  • @rtdietrich
    @rtdietrich 5 месяцев назад

    Great Video!!!!

  • @emvdl
    @emvdl 2 года назад

    Thanks! 🤙

  • @idiot7leon
    @idiot7leon 2 года назад

    Thanks!

  • @viacheslav1392
    @viacheslav1392 2 года назад +2

    Привіт світ - was great!)

  • @joehsiao6224
    @joehsiao6224 Год назад

    Great content! Can you do a video of chars in Rust and unicode scalar values? After hours of searching on the Internet, I am still confused.

  • @menardmaranan9356
    @menardmaranan9356 2 года назад

    What's the extension you're using for type autocomplete?

  • @jonathanmoore5619
    @jonathanmoore5619 2 года назад +3

    Let's get goddamn rusty!

  • @coolbrotherf127
    @coolbrotherf127 Год назад +2

    I started with creating char arrays in C back in the day so this isn't too crazy compared to that.

  • @tobi9648
    @tobi9648 5 месяцев назад

    I really like all of your videos they are one of the best out there. You mentioned that you came from a javascript background. How did you become a pro-rust-developer? did you learn the language privately and than you searched for a job or was it a fluent transition within the company you've worked in? I'm prof. C# /Typescript/Javascript developer and would like to jump on the rust-train :-)

  • @partisan-bobryk
    @partisan-bobryk 2 года назад +9

    🇺🇦 Thank you for the explanation and examples!

  • @Captainlonate
    @Captainlonate Год назад

    How do you get VSCode to show the type annotations for let bindings automatically?

  • @jimshtepa5423
    @jimshtepa5423 2 года назад

    Богдан, спасибо! очень крутой материал

  • @shukterhousejive
    @shukterhousejive 2 года назад +2

    How does Rust handle interoperability between string implementations (OSString, CString, a hypothetical UTF-32 String etc.)? Is there enough compiler sugar to pass a reference to an alternate String type to &str, or is there an "IString" trait you can implement or is there a lot of myString.as_str() involved?

    • @valthorhalldorsson9300
      @valthorhalldorsson9300 2 года назад +3

      it’s always manual conversions, though you have a lot of options depending on what you need - rust is super strict about not performing expensive conversions automatically (especially ones that can fail, like converting a byte string to a utf-8 string)

    • @xrafter
      @xrafter 2 года назад +1

      usually you will see something like AsRef in th3 standard library

  • @minecrafter8863
    @minecrafter8863 2 года назад

    Hey bro, thanks for video. Do you use rust for blockchain development? The guys at Solana would love to have a series on that development on solana!

  • @tanuvishu
    @tanuvishu 2 года назад

    This is the first time I really understood UTF-8

  • @theana5550
    @theana5550 2 года назад

    Okay... it's been about a month, but you can just use a Vec for constant time lookups.

    • @letsgetrusty
      @letsgetrusty  2 года назад +2

      A char does not equal a user perceived character. 1 user perceived character can be multiple chars.

    • @theana5550
      @theana5550 2 года назад

      @@letsgetrusty yeah, true

  • @qwerwasd1724
    @qwerwasd1724 2 месяца назад

    I'm a little late, but what is the binary? And how does it differ from the stack and heap?

  • @mr.x5582
    @mr.x5582 10 месяцев назад

    goated content

  • @hytryi_huy
    @hytryi_huy Год назад

    10:37 wow that's so cute, thank you))

  • @available898
    @available898 Год назад

    interesting that you see ASCII as a map from integers to characters and not a map from characters to integers :)

  • @winsonleow9660
    @winsonleow9660 Год назад

    I am trying switch over as well from nodejs still struggling with rust..

  • @g2mXagent
    @g2mXagent 8 месяцев назад

    How do you enter emoji in your code?

  • @foobar1269
    @foobar1269 2 года назад

    String in Rust made my head spin. Coming from Ruby and Python string is very easy.

    • @alagaika8515
      @alagaika8515 Год назад

      I have been using Python since the Python 2 days and while it seemed simpler by trying to automatically convert between unicode and bytes, this was a source for really confusing errors. In fact, Python 3 became more strict in this area by introducing the same separation between bytes and (character) strings that Rust is essentially using - which saves programmers from a lot of hard to track errors. The difference is that in Python, indexing into a string now counts the characters, which has an unexpected complexity, while Rust counts the bytes and then checks that the result is valid.

  • @alexandershemelin6605
    @alexandershemelin6605 2 года назад

    cool man

  • @ksnyou
    @ksnyou 2 года назад +1

    18:03 Namaste!

  • @salvadorvillarreal1643
    @salvadorvillarreal1643 Год назад

    I know about the stack and the heap (and the register), but I've never heard of the "application's binary". What is it? Do you have another video where you explain it?
    Thanks!

    • @dynfoxx
      @dynfoxx Год назад +1

      There are a few different ways a program can get read only or read write memory.
      Your executable normally contains a chunks or describes chunks or memory that it needs.
      The OS then copies or creates these chunks from your program and marks them as read only or read write.
      They are different sections of the program then heap and stack. That's the basics hope it makes sense.

    • @salvadorvillarreal1643
      @salvadorvillarreal1643 Год назад

      @@dynfoxx Definitely! Thank you!

  • @dibyojyotibhattacherjee4279
    @dibyojyotibhattacherjee4279 2 года назад

    Hey do u get the warning that says something like could not access incremental compilation directory?.

    • @letsgetrusty
      @letsgetrusty  2 года назад +2

      Nope

    • @proloycodes
      @proloycodes 2 года назад

      looks like you are using termux cuz i also get that

  • @736939
    @736939 Год назад

    "Applications binary" - is is the a static memory?

  • @PeterPerhac
    @PeterPerhac Год назад

    Y U no have THANKS button? I would have bought you a pint! Thanks mate, fantastic explanation. Had some aha moments. Thanks again

  • @Maaruks
    @Maaruks Год назад

    What is string slice?

  • @TheRealWinsletFan
    @TheRealWinsletFan 29 дней назад

    The proliferation of code pages was an issue many years before the world wide web hit critical mass. Not everyone developed code to run in only one Country/Region.

  • @skyeplus
    @skyeplus 2 года назад +1

    As far as I understand in Go you take a string which is a slice of bytes and build another slice of int32, 1 per each unicode code points. So it's nothing special, or advantegious. You just paid for a a full string decoding once. This would be equivalent to chars().map(|c| c as u32).collect::() in Rust.
    But because in Rust iterators are lazy you don't pay for creation of a new vector each time unless you explicitly want to.

    • @proloycodes
      @proloycodes 2 года назад +1

      shouldn't it be `.chars().map(|x| x as u32).collect::()`?

    • @skyeplus
      @skyeplus 2 года назад +1

      @@proloycodes You're correct. Fixed.

  • @johnpett524
    @johnpett524 2 года назад +1

    Excuse me! Can you tell me how your vscode can show type of rust variable on the left side? Thanks a lot

  • @Maaruks
    @Maaruks Год назад

    what is byte?

  • @LinuThomas
    @LinuThomas 5 месяцев назад

    Nice Video :) GOD Help

  • @yapayzeka
    @yapayzeka Год назад

    this channel is a blessing. best explanation out there. thank you

  • @kishanbsh
    @kishanbsh 2 года назад +1

    Definitely learnt unicode.. thanks!!.. wondering how this complexity is being hidden in other languages 🤔..

    • @xrafter
      @xrafter 2 года назад

      They usually hide this checking and validation away from you .
      in js for example the engine will do it .

    • @trejkaz
      @trejkaz Год назад

      @@xrafter In JS they just let you make the mistakes. "🤔".length == 2, for example. And to split grapheme clusters, you will have to go find an external library, because it can't do that at all.
      The only thing other languages can do is streamline the usage of the complex stuff. In Elixir, for example, they hide the concept of string normalisation by making string equality work correctly out of the box. They still have an iterator over grapheme clusters but it's in the core library so you don't have to pull a separate dependency to get it.
      Older languages as a rule just get string handling wrong and push the complexity to the developer. It isn't so much hidden, as completely absent.

  • @hoverpillow6106
    @hoverpillow6106 9 месяцев назад

    Дякую за вашу працю.

  • @fabianmallmann4834
    @fabianmallmann4834 Год назад

    Good stuff! Just getting into Rust as a mainly JS-Dev and this is by far the best content out there! One question: What VS-Code extension are you using, that shows these greyed out type-annotations and function-parameters?

    • @nic37ry
      @nic37ry Год назад

      It's built-in in vs code and it's generated by the Language Server and it's called inlay hints

  • @Chastor97
    @Chastor97 Год назад

    wow. I'm from JS too

  • @Maaruks
    @Maaruks Год назад

    ACII ? what is ascii?

  • @Maaruks
    @Maaruks Год назад

    what is emoji?

  • @abcxyz5806
    @abcxyz5806 Год назад

    Why is there both to_string() and to_owned()?

  • @romanmahotskyi6898
    @romanmahotskyi6898 Год назад +1

    Дякую (:

  • @Maaruks
    @Maaruks Год назад

    please teach me about integers? what are integers?

  • @Maaruks
    @Maaruks Год назад

    Whats is STR?

  • @dorktales254
    @dorktales254 2 года назад

    Nice shirt

  • @Maaruks
    @Maaruks Год назад

    whats is binary?

  • @gamer-gw9iy
    @gamer-gw9iy 2 года назад

    Let's get rusty!

  • @farleylai1102
    @farleylai1102 Год назад

    Why to expose the complexities of UTF8 encoding to programmers when most of time it is the user perceived chars to deal with?

  • @Maaruks
    @Maaruks Год назад

    whats is a computer?

  • @marcorodrigues1331
    @marcorodrigues1331 2 года назад +1

    What intrigues me is that, according to that explanation, in UTF-8 we should have 2,164,864 possibilities and not 1,112,064. So lets see:
    - 1 byte characters (0xxxxxxx): 2^(7) possibilities = 128 possibilities
    - 2 bytes characters (110xxxxx 10xxxxxx): 2^(11) possibilities = 2,048 possibilities
    - 3 bytes characters (1110xxxx 10xxxxxx 10xxxxxx): 2^(16) possibilities = 65,536 possibilities
    - 4 bytes characters (11110xxx 10xxxxxx 10xxxxxx 10xxxxxx): 2^(21) possibilities = 2,097,152 possibilities
    Total possibilities of characters: 128 + 2,048 + 65,536 + 2,097,152 = 2,164,864
    So why 1,112,064?

    • @MalleusImperiorum
      @MalleusImperiorum 2 года назад +5

      In 2003, the RFC 3629 standard for UTF-8 restricted the possible range of symbols by U+10FFFF and excluded U+D800..U+DFFF from the range, to make UTF-8 compatible with UTF-16.

    • @marcorodrigues1331
      @marcorodrigues1331 2 года назад

      @@MalleusImperiorum thanks for the clarification!

  • @MrWandalen
    @MrWandalen 2 года назад +5

    Привіт!

    • @letsgetrusty
      @letsgetrusty  2 года назад +3

      Привіт!

    • @denysmazhar5990
      @denysmazhar5990 2 года назад +2

      @@letsgetrusty Huh, I knew that you're from Ukraine when first saw your video couple of weeks ago :) Hi from Kyiv and thanks you for this content!

    • @letsgetrusty
      @letsgetrusty  2 года назад +3

      @@denysmazhar5990 Welcome aboard! I'm from Lviv :)

    • @VitaliySapronenko
      @VitaliySapronenko Год назад

      @@letsgetrusty Гарна вимова! Нездогадався б що ти з України, якби не побачив натяків у відео.

  • @milind_patil
    @milind_patil 2 года назад

    I love the Namaste 🙏