WEBVTT

1
00:00:00.120 --> 00:00:04.639
<v Speaker 1>You know Python. You love its incredible flexibility, its massive ecosystem,

2
00:00:05.000 --> 00:00:07.519
<v Speaker 1>and how quickly you can bring an idea to life,

3
00:00:07.599 --> 00:00:09.199
<v Speaker 1>right from concept to prototype.

4
00:00:09.199 --> 00:00:11.119
<v Speaker 2>Absolutely, it's fantastic for getting things done.

5
00:00:11.199 --> 00:00:14.519
<v Speaker 1>But what happens when that speed, the execution speed, becomes

6
00:00:14.640 --> 00:00:18.120
<v Speaker 1>absolutely critical. What if your Python code starts hitting a wall,

7
00:00:18.320 --> 00:00:20.039
<v Speaker 1>becoming the bottleneck in your system?

8
00:00:20.199 --> 00:00:21.480
<v Speaker 2>Yeah, that's a common problem.

9
00:00:21.519 --> 00:00:27.160
<v Speaker 1>Today we're diving deep into a fascinating, powerful solution, supercharging

10
00:00:27.199 --> 00:00:29.120
<v Speaker 1>your Python applications with Rust.

11
00:00:29.359 --> 00:00:31.000
<v Speaker 2>It's a really interesting combination.

12
00:00:31.320 --> 00:00:33.719
<v Speaker 1>Welcome to the deep Dive, the show where we take

13
00:00:33.719 --> 00:00:37.000
<v Speaker 1>your sources, the articles, the research, your own notes and

14
00:00:37.119 --> 00:00:40.039
<v Speaker 1>extract the most important nuggets of knowledge to make you

15
00:00:40.119 --> 00:00:41.679
<v Speaker 1>truly well informed.

16
00:00:41.399 --> 00:00:43.320
<v Speaker 2>Fast, getting you up to speed quickly.

17
00:00:43.560 --> 00:00:46.560
<v Speaker 1>Our mission today is to unpack the practical art of

18
00:00:46.679 --> 00:00:53.200
<v Speaker 1>fusing Python's legendary agility with rusts well unparalleled performance and safety.

19
00:00:53.439 --> 00:00:55.200
<v Speaker 1>We're not just going to tell you how they work together.

20
00:00:55.479 --> 00:00:59.520
<v Speaker 1>We'll explore why this combination can fundamentally revolutionize your approach

21
00:00:59.560 --> 00:01:01.000
<v Speaker 1>to building efficient software.

22
00:01:01.039 --> 00:01:03.520
<v Speaker 2>And we've got some really cool real world examples too.

23
00:01:03.719 --> 00:01:05.480
<v Speaker 1>Oh yeah, we're going to hit you with some surprising

24
00:01:05.519 --> 00:01:07.879
<v Speaker 1>ones that might just change how you think about programming

25
00:01:07.959 --> 00:01:11.120
<v Speaker 1>languages forever. So let's unpack this, let's do it. Python

26
00:01:11.159 --> 00:01:13.719
<v Speaker 1>as we know is truly amazing. It's the go to

27
00:01:13.959 --> 00:01:17.640
<v Speaker 1>for rapid prototyping, building complex logic.

28
00:01:17.400 --> 00:01:20.280
<v Speaker 2>And the libraries, the ecosystems.

29
00:01:19.640 --> 00:01:24.079
<v Speaker 1>Just vast exactly. It's flexible, object oriented programming is ideal

30
00:01:24.159 --> 00:01:28.560
<v Speaker 1>for solving real world problems quickly. But here's the unavoidable truth,

31
00:01:29.000 --> 00:01:32.920
<v Speaker 1>the double edged sword, uh huh, the speed. It's notoriously

32
00:01:33.000 --> 00:01:36.200
<v Speaker 1>slow and not always the most efficient with system resources.

33
00:01:36.719 --> 00:01:39.640
<v Speaker 1>This is where, especially with the explosion of big data

34
00:01:39.680 --> 00:01:44.480
<v Speaker 1>and computationally intensive tasks, the need for faster, more performance

35
00:01:44.560 --> 00:01:46.159
<v Speaker 1>languages becomes undeniable.

36
00:01:46.239 --> 00:01:49.200
<v Speaker 2>Precisely, and this is exactly where RUSS steps onto the

37
00:01:49.200 --> 00:01:52.840
<v Speaker 2>stage and truly shines. It's fundamentally memory safe. It compiles

38
00:01:52.879 --> 00:01:56.480
<v Speaker 2>directly to machine code, giving you raw, unadulterated.

39
00:01:55.760 --> 00:01:57.359
<v Speaker 1>Speed right direct compilation.

40
00:01:57.760 --> 00:02:01.359
<v Speaker 2>But here's the game changer, what makes RUSS utterly unique.

41
00:02:01.920 --> 00:02:05.719
<v Speaker 2>It achieves all that memory safety without relying on garbage collection.

42
00:02:05.879 --> 00:02:07.400
<v Speaker 1>Ah. The GC explain that.

43
00:02:07.359 --> 00:02:10.840
<v Speaker 2>A bit sure for those unfamiliar, garbage collection is like

44
00:02:11.080 --> 00:02:14.280
<v Speaker 2>a program periodically hitting the pause button to sweep up

45
00:02:14.360 --> 00:02:18.599
<v Speaker 2>unused variables and free up memory. It's necessary in many languages,

46
00:02:18.639 --> 00:02:20.360
<v Speaker 2>but it can cause pauses.

47
00:02:20.000 --> 00:02:22.360
<v Speaker 1>Unpredictable pauses sometimes exactly.

48
00:02:22.879 --> 00:02:27.400
<v Speaker 2>Rust elegantly sidesteps this, meaning no pauses, no unpredictable spikes

49
00:02:27.400 --> 00:02:31.280
<v Speaker 2>and latency, just consistent, blazing fast performance.

50
00:02:31.439 --> 00:02:34.639
<v Speaker 1>That's a huge operational advantage, and we've seen this validated

51
00:02:34.639 --> 00:02:37.199
<v Speaker 1>in the real world in some pretty compelling ways, haven't we.

52
00:02:37.319 --> 00:02:37.960
<v Speaker 2>Oh, definitely.

53
00:02:38.080 --> 00:02:41.520
<v Speaker 1>I immediately think of Discord's twenty twenty blog post why

54
00:02:41.599 --> 00:02:43.719
<v Speaker 1>Discord is switching from Go to Rust.

55
00:02:43.840 --> 00:02:48.400
<v Speaker 2>Absolutely. Discord's experience is a perfect illustration. They observe that Go,

56
00:02:48.919 --> 00:02:53.919
<v Speaker 2>while fast, had spiky performance, meeting inconsistent and unpredictable latency.

57
00:02:53.479 --> 00:02:55.039
<v Speaker 1>Which is bad for real time chat.

58
00:02:55.360 --> 00:02:58.360
<v Speaker 2>Exactly, Rust, on the other hand, delivered a flat line

59
00:02:58.560 --> 00:03:02.120
<v Speaker 2>in their performance graphs, consistently low latency, which is critical

60
00:03:02.120 --> 00:03:04.159
<v Speaker 2>for a real time communication platform.

61
00:03:04.240 --> 00:03:07.039
<v Speaker 1>And didn't some people argue about the Go version they used.

62
00:03:07.280 --> 00:03:10.039
<v Speaker 2>Yeah, there was some pushback, but Discord confirmed they had

63
00:03:10.159 --> 00:03:13.120
<v Speaker 2>rigorously tested a range of Go versions and they all

64
00:03:13.159 --> 00:03:18.719
<v Speaker 2>produced similar spiky results. This truly reinforced Rust's consistent performance

65
00:03:18.800 --> 00:03:19.319
<v Speaker 2>edge for them.

66
00:03:19.879 --> 00:03:22.680
<v Speaker 1>So if we connect these dots, what does this all

67
00:03:22.719 --> 00:03:26.199
<v Speaker 1>boil down to for us? As developers. What's the takeaway.

68
00:03:26.520 --> 00:03:28.800
<v Speaker 2>It's about achieving the best of both worlds. Really, the

69
00:03:28.840 --> 00:03:31.599
<v Speaker 2>goal isn't to replace Python, not at all. It's about

70
00:03:31.639 --> 00:03:35.080
<v Speaker 2>augmenting it. Okay, you use Python for what it excels

71
00:03:35.080 --> 00:03:38.840
<v Speaker 2>at rapp development, complex high level logic, orchestrating your application,

72
00:03:39.240 --> 00:03:42.680
<v Speaker 2>and then when you hit a performance bottleneck, you strategically reach.

73
00:03:42.560 --> 00:03:44.840
<v Speaker 1>For Rust like a surgical strike for speed.

74
00:03:44.960 --> 00:03:48.240
<v Speaker 2>Precisely this way you get the unparalleled efficiency and speed

75
00:03:48.639 --> 00:03:52.439
<v Speaker 2>without compromising Python's developer friendly experience too much.

76
00:03:52.599 --> 00:03:56.520
<v Speaker 1>That vision sounds incredibly appealing, speed without the usual headaches,

77
00:03:56.960 --> 00:03:59.560
<v Speaker 1>but that speed and safety and rust come at a price, right,

78
00:03:59.639 --> 00:04:01.280
<v Speaker 1>And that the price is strictness.

79
00:04:02.000 --> 00:04:03.319
<v Speaker 2>Yes, it's definitely stricter.

80
00:04:03.719 --> 00:04:06.280
<v Speaker 1>As a Python developer, we're used to the incredible ease

81
00:04:06.319 --> 00:04:08.719
<v Speaker 1>of mixing types, like just adding one plus two point

82
00:04:08.759 --> 00:04:12.280
<v Speaker 1>two seamlessly. How does Rust approach that fundamental concept?

83
00:04:12.479 --> 00:04:15.479
<v Speaker 2>This is often the very first hurdle of Python developer encounters.

84
00:04:16.040 --> 00:04:18.920
<v Speaker 2>Rust enforces what we call aggressive typing.

85
00:04:19.000 --> 00:04:19.680
<v Speaker 1>GRIFFI is typing.

86
00:04:19.759 --> 00:04:21.720
<v Speaker 2>Okay, if you tried let result one plus two point

87
00:04:21.759 --> 00:04:25.600
<v Speaker 2>two in Rust, the compiler would immediately throw an error.

88
00:04:26.120 --> 00:04:28.480
<v Speaker 2>It won't let you mix an integer and a floating

89
00:04:28.480 --> 00:04:29.759
<v Speaker 2>point number directly like that.

90
00:04:29.920 --> 00:04:31.319
<v Speaker 1>No implicit conversion.

91
00:04:31.399 --> 00:04:34.959
<v Speaker 2>Nope, this isn't just about being pedantic. This aggressive typing

92
00:04:35.160 --> 00:04:37.639
<v Speaker 2>ensures a level of safety in the long run that

93
00:04:37.720 --> 00:04:41.839
<v Speaker 2>Python's dynamic nature can't match, because it catches entire classes

94
00:04:41.839 --> 00:04:45.000
<v Speaker 2>of bugs before your code even run that compile time exactly.

95
00:04:45.079 --> 00:04:48.480
<v Speaker 2>Another immediate difference is that variables in Rust are automatically

96
00:04:49.000 --> 00:04:52.759
<v Speaker 2>immutable by default. You have to explicitly declare them with

97
00:04:52.879 --> 00:04:55.560
<v Speaker 2>MUTT if you intend to change their value.

98
00:04:55.360 --> 00:04:57.399
<v Speaker 1>So you have to opt into mutability right.

99
00:04:57.720 --> 00:05:02.360
<v Speaker 2>And this strictness extends to numbers themselves. Rust differentiates between

100
00:05:02.439 --> 00:05:04.639
<v Speaker 2>signed integers like I eight or I one twenty eight,

101
00:05:04.680 --> 00:05:07.000
<v Speaker 2>which can hold both positive and negative.

102
00:05:06.639 --> 00:05:09.160
<v Speaker 1>Values like regular integers and Python.

103
00:05:08.800 --> 00:05:11.439
<v Speaker 2>Mostly kind of yeah, and unsigned integers you eight or

104
00:05:11.560 --> 00:05:14.120
<v Speaker 2>U one twenty eight, which only old non negative numbers.

105
00:05:14.519 --> 00:05:17.480
<v Speaker 2>This impacts their range. For example, you eight can hold

106
00:05:17.720 --> 00:05:19.959
<v Speaker 2>zero two of fifty five. If you try to let

107
00:05:20.040 --> 00:05:23.680
<v Speaker 2>braking number you eight equals two to fifty six, the

108
00:05:23.720 --> 00:05:27.040
<v Speaker 2>compiler will simply tell you it doesn't fit Bam, compile error.

109
00:05:27.079 --> 00:05:28.240
<v Speaker 1>It stops you right there.

110
00:05:28.360 --> 00:05:30.800
<v Speaker 2>For floats, you have F thirty two and F sixty four.

111
00:05:31.720 --> 00:05:34.879
<v Speaker 2>This explicit typing allows Rust to be incredibly efficient with

112
00:05:35.000 --> 00:05:38.120
<v Speaker 2>memory and execution because it knows exactly what kind of

113
00:05:38.199 --> 00:05:40.120
<v Speaker 2>data it's dealing with at all times.

114
00:05:39.839 --> 00:05:42.600
<v Speaker 1>So it's not just about simple variables. How does this

115
00:05:42.839 --> 00:05:46.639
<v Speaker 1>strictness ripple through to something as fundamental as data structures?

116
00:05:47.279 --> 00:05:50.800
<v Speaker 1>In Python? Lists are incredibly flexible. We can throw anything

117
00:05:50.839 --> 00:05:51.519
<v Speaker 1>you want into them.

118
00:05:51.680 --> 00:05:54.399
<v Speaker 2>Yeah, strings, numbers, objects.

119
00:05:53.800 --> 00:05:57.959
<v Speaker 1>And they're mutable by default. Tuples are like immutable arrays.

120
00:05:58.000 --> 00:05:59.079
<v Speaker 1>How does Rust handle this?

121
00:05:59.439 --> 00:06:02.120
<v Speaker 2>Rust's a roach to data structures like arrays in vectors

122
00:06:02.199 --> 00:06:05.240
<v Speaker 2>is all about control and consistency. A rasor fixed in size,

123
00:06:05.240 --> 00:06:07.279
<v Speaker 2>and crucially, all their elements must be of the same

124
00:06:07.360 --> 00:06:08.639
<v Speaker 2>type or implement a.

125
00:06:08.560 --> 00:06:10.800
<v Speaker 1>Consistent trait same type only got it.

126
00:06:11.120 --> 00:06:14.319
<v Speaker 2>Vectors are Rust's equivalent of a dynamic array. They're expandable

127
00:06:14.319 --> 00:06:16.680
<v Speaker 2>and live on the heap, but just like variables, they're

128
00:06:16.680 --> 00:06:19.759
<v Speaker 2>immutable by default. You need that mutt keyword to add

129
00:06:19.839 --> 00:06:21.040
<v Speaker 2>or manipulate.

130
00:06:20.519 --> 00:06:22.279
<v Speaker 1>Elements still upt in for changes.

131
00:06:22.680 --> 00:06:26.279
<v Speaker 2>Right. The why behind this consistency is paramount rusts type

132
00:06:26.360 --> 00:06:30.480
<v Speaker 2>enforcement at compile time prevents runtime errors. Imagine trying to

133
00:06:30.480 --> 00:06:33.560
<v Speaker 2>perform a mathematical operation on a list in Python that

134
00:06:33.720 --> 00:06:35.879
<v Speaker 2>unexpectedly contains a string, it would crash.

135
00:06:36.160 --> 00:06:38.800
<v Speaker 1>Yeah, the dreaded typer runtime.

136
00:06:38.480 --> 00:06:40.439
<v Speaker 2>RUSS simply won't let you build that in the first place,

137
00:06:40.600 --> 00:06:41.959
<v Speaker 2>ensuring predictable behavior.

138
00:06:42.120 --> 00:06:44.040
<v Speaker 1>That makes a lot of sense. So if you do

139
00:06:44.160 --> 00:06:47.240
<v Speaker 1>need to store multiple different types of data in a

140
00:06:47.279 --> 00:06:50.800
<v Speaker 1>container and rust, like in a Python dictionary where values

141
00:06:50.800 --> 00:06:54.199
<v Speaker 1>could be strings or integers, how do you manage that

142
00:06:54.240 --> 00:06:57.319
<v Speaker 1>without running a foul of these stripped type rules.

143
00:06:57.519 --> 00:07:00.519
<v Speaker 2>That's where enoms come in, short for enumeration. Think of

144
00:07:00.519 --> 00:07:02.839
<v Speaker 2>an enom as a way to say this value could

145
00:07:02.839 --> 00:07:04.120
<v Speaker 2>be one of these specific types.

146
00:07:04.360 --> 00:07:07.040
<v Speaker 1>Okay, like defining the possibilities exactly.

147
00:07:07.160 --> 00:07:10.399
<v Speaker 2>So you might define an enom called value that can

148
00:07:10.439 --> 00:07:14.560
<v Speaker 2>be either value dot string or value dot nti three two.

149
00:07:15.360 --> 00:07:18.399
<v Speaker 2>Then to safely extract the value, RUSS requires you to

150
00:07:18.439 --> 00:07:19.079
<v Speaker 2>use a match.

151
00:07:18.920 --> 00:07:23.160
<v Speaker 1>Statement match like a switch statement, similar but more powerful.

152
00:07:23.800 --> 00:07:26.720
<v Speaker 1>This match statement is like a supercharged switch, but with

153
00:07:26.839 --> 00:07:30.720
<v Speaker 1>a critical difference. The compiler forces you to handle every

154
00:07:30.759 --> 00:07:35.160
<v Speaker 1>single possible outcome that your enom could represent, every single case,

155
00:07:35.319 --> 00:07:38.959
<v Speaker 1>every single one. If you miss a case, it won't compile.

156
00:07:39.600 --> 00:07:43.079
<v Speaker 1>This provides an incredibly robust safety net, ensuring you don't

157
00:07:43.120 --> 00:07:46.600
<v Speaker 1>accidentally overlook a scenario. Wow, you can use the pattern

158
00:07:46.759 --> 00:07:50.160
<v Speaker 1>like an underscore to catch any unhandled cases or values

159
00:07:50.199 --> 00:07:53.279
<v Speaker 1>you genuinely don't care about, which keeps the compiler happy.

160
00:07:53.639 --> 00:07:57.000
<v Speaker 2>Error handling is another crucial aspect of writing Roebus software.

161
00:07:57.519 --> 00:08:01.399
<v Speaker 2>Pythons try except blocks are pretty straightforward. How does Rust

162
00:08:01.399 --> 00:08:05.319
<v Speaker 2>approach errors? And what about memory safety? That's like Rust's.

163
00:08:04.959 --> 00:08:07.759
<v Speaker 1>Superpower, right, yeah, it really is. Rust handles errors with

164
00:08:07.800 --> 00:08:11.040
<v Speaker 1>a result type. It's a bit like Python's none for

165
00:08:11.079 --> 00:08:13.879
<v Speaker 1>missing values may be, but much more explicit. How so,

166
00:08:14.480 --> 00:08:16.560
<v Speaker 1>our result is a container that either holds an OK

167
00:08:16.800 --> 00:08:20.399
<v Speaker 1>value meaning success and it contains the successful result, or

168
00:08:20.439 --> 00:08:23.600
<v Speaker 1>an error value indicating that something went wrong, containing the

169
00:08:23.720 --> 00:08:24.399
<v Speaker 1>error information.

170
00:08:24.519 --> 00:08:26.000
<v Speaker 2>So you have to deal with the possibility of an

171
00:08:26.079 --> 00:08:29.759
<v Speaker 2>error exactly. It forces you to explicitly acknowledge and handle

172
00:08:29.759 --> 00:08:33.559
<v Speaker 2>potential errors. But the real magic, the core of Rust's

173
00:08:33.600 --> 00:08:38.399
<v Speaker 2>promise lies in its compile time memory protection. Rust's compiler

174
00:08:38.519 --> 00:08:41.559
<v Speaker 2>is a vigilant guardian the borrow checker, that's the one.

175
00:08:41.919 --> 00:08:44.960
<v Speaker 2>It aggressively checks your code to prevent a whole host

176
00:08:45.120 --> 00:08:49.799
<v Speaker 2>of common, notoriously difficult to debug memory errors. We're talking

177
00:08:49.799 --> 00:08:51.720
<v Speaker 2>about use after freeze.

178
00:08:51.399 --> 00:08:53.440
<v Speaker 1>Using memory after you said you were done.

179
00:08:53.320 --> 00:08:57.000
<v Speaker 2>With it, right, dangling pointers where references point to non

180
00:08:57.039 --> 00:09:01.799
<v Speaker 2>existed data, double freeze, freeing memory twice, segmentation faults accessing

181
00:09:01.799 --> 00:09:05.279
<v Speaker 2>memory you shouldn't, and buffer overruns reading beyond the bounds

182
00:09:05.320 --> 00:09:05.799
<v Speaker 2>of an array.

183
00:09:05.919 --> 00:09:07.480
<v Speaker 1>Nasky bugs, horrible ones.

184
00:09:07.639 --> 00:09:11.200
<v Speaker 2>RUSS catches these before your program even runs at compile time.

185
00:09:11.360 --> 00:09:14.080
<v Speaker 1>That's an impressive list of safeguards. So what are the

186
00:09:14.120 --> 00:09:18.399
<v Speaker 1>fundamental rules that rustin forces to achieve this incredible memory safety?

187
00:09:18.679 --> 00:09:19.840
<v Speaker 1>How does it actually work?

188
00:09:19.919 --> 00:09:22.960
<v Speaker 2>There are a few core tenets that simplify Rust's approach,

189
00:09:23.320 --> 00:09:26.519
<v Speaker 2>though they take getting used to. First, every value in

190
00:09:26.639 --> 00:09:29.519
<v Speaker 2>Rust has an owner, which is the variable assigned to it.

191
00:09:29.559 --> 00:09:30.679
<v Speaker 1>One owner per value.

192
00:09:31.000 --> 00:09:35.120
<v Speaker 2>Yes. Second, when that owner variable goes at a scope,

193
00:09:35.440 --> 00:09:37.759
<v Speaker 2>like at the end of a function or block, the

194
00:09:37.840 --> 00:09:40.879
<v Speaker 2>memory it occupies is automatically deallocated. You don't have to

195
00:09:40.919 --> 00:09:45.519
<v Speaker 2>manage it manually automatic cleanup right, and Third, values can

196
00:09:45.559 --> 00:09:48.360
<v Speaker 2>be used by other variables, but only if you adhere

197
00:09:48.399 --> 00:09:54.279
<v Speaker 2>to very specific conventions. Copying moving, immutable borrowing, immutable borrowing.

198
00:09:54.360 --> 00:09:58.399
<v Speaker 1>Okay, copy move borrow. Let's break that down copying versus moving.

199
00:09:58.600 --> 00:10:01.759
<v Speaker 2>Let's illustrate with strings. Unlike a simple number like an

200
00:10:01.799 --> 00:10:05.320
<v Speaker 2>I thirty two, a string and Rust doesn't implement the

201
00:10:05.360 --> 00:10:08.120
<v Speaker 2>copy trait. Think of a string as a physical book,

202
00:10:08.200 --> 00:10:10.799
<v Speaker 2>not a photograph. If I give you the book.

203
00:10:10.759 --> 00:10:12.639
<v Speaker 1>You don't have it anymore, exactly, it's moved.

204
00:10:12.840 --> 00:10:15.399
<v Speaker 2>If I then try to read from my original book variable,

205
00:10:15.600 --> 00:10:18.120
<v Speaker 2>the compiler will stop me. It'll say value borrowed here

206
00:10:18.159 --> 00:10:18.639
<v Speaker 2>after move.

207
00:10:18.679 --> 00:10:19.600
<v Speaker 1>Why not just copy it?

208
00:10:19.679 --> 00:10:22.679
<v Speaker 2>Because copying the underlying pointer could be dangerous. You'd have

209
00:10:22.759 --> 00:10:25.759
<v Speaker 2>multiple pointers to the same mutable data. If one changes that,

210
00:10:25.840 --> 00:10:28.799
<v Speaker 2>the others might break. So if you truly want a separate,

211
00:10:28.840 --> 00:10:32.080
<v Speaker 2>independent copy of a string, a deep copy, you explicitly

212
00:10:32.159 --> 00:10:33.159
<v Speaker 2>us dot on tound.

213
00:10:33.440 --> 00:10:37.639
<v Speaker 1>Okay, So moving is the default for complex types like strings.

214
00:10:38.279 --> 00:10:40.879
<v Speaker 1>What about borrowing? That sounds like a key distinction.

215
00:10:41.039 --> 00:10:44.559
<v Speaker 2>It absolutely is. It's central to rust. Think of it

216
00:10:44.679 --> 00:10:47.600
<v Speaker 2>like lending that book out instead of giving it away.

217
00:10:47.639 --> 00:10:51.159
<v Speaker 2>With an immutable borrow, you're lending someone a copy to read.

218
00:10:51.960 --> 00:10:55.639
<v Speaker 2>Multiple people can have immutable borrows read only access at

219
00:10:55.679 --> 00:10:56.279
<v Speaker 2>the same.

220
00:10:56.120 --> 00:10:58.039
<v Speaker 1>Time, with lots of readers no writers.

221
00:10:58.039 --> 00:11:02.240
<v Speaker 2>Correct the original variable can still be copied or immutably borrowed,

222
00:11:02.559 --> 00:11:05.600
<v Speaker 2>but it cannot be mutably borrowed while immutable borrows exist.

223
00:11:06.240 --> 00:11:07.600
<v Speaker 2>Now with a mutable.

224
00:11:07.279 --> 00:11:10.080
<v Speaker 1>Borrow, like letting someone edit the book exactly.

225
00:11:09.679 --> 00:11:12.000
<v Speaker 2>You're giving someone exclusive access to that book so they

226
00:11:12.039 --> 00:11:15.039
<v Speaker 2>can make changes. Only one mutable borrow can exist at

227
00:11:15.039 --> 00:11:19.000
<v Speaker 2>a time, only one editor, and crucially, the original variable

228
00:11:19.039 --> 00:11:21.679
<v Speaker 2>cannot be used at all, not read, not borrowed again

229
00:11:21.759 --> 00:11:25.159
<v Speaker 2>while it's mutably borrowed, because its state might be altered unpredictably.

230
00:11:25.879 --> 00:11:28.799
<v Speaker 2>This prevents conflicts and ensures data integrity.

231
00:11:28.919 --> 00:11:31.639
<v Speaker 1>Wow, so either many readers or one writer.

232
00:11:31.919 --> 00:11:32.840
<v Speaker 2>That's the core rule.

233
00:11:33.080 --> 00:11:36.320
<v Speaker 1>All these rules around ownership and borrowing seem to tie

234
00:11:36.320 --> 00:11:39.639
<v Speaker 1>into the concept of scopes and lifetimes, which can be

235
00:11:39.679 --> 00:11:42.279
<v Speaker 1>a bit of a head scratcher for Python developers, where

236
00:11:42.320 --> 00:11:45.000
<v Speaker 1>scope is primarily enforced within functions.

237
00:11:45.240 --> 00:11:48.440
<v Speaker 2>You're right, it's a shift in thinking. Rust has very

238
00:11:48.480 --> 00:11:52.480
<v Speaker 2>strict scoping rules. Variables are dropped, meaning your memory is

239
00:11:52.519 --> 00:11:55.279
<v Speaker 2>deallocated as soon as they exit the scope where they

240
00:11:55.279 --> 00:11:59.159
<v Speaker 2>were created. Like the curly braces. Define scopes very strictly.

241
00:11:59.000 --> 00:12:01.120
<v Speaker 1>And this leads to the lifetime problem often.

242
00:12:01.200 --> 00:12:04.720
<v Speaker 2>Yes, it's a classic example. Imagine you declare a variable

243
00:12:04.759 --> 00:12:08.360
<v Speaker 2>one outside of scope, then inside an interscope you create

244
00:12:08.399 --> 00:12:11.879
<v Speaker 2>two and make one borrow a reference to two. Okay,

245
00:12:11.960 --> 00:12:15.000
<v Speaker 2>when that interscope ends, two is dropped. Its memory is gone,

246
00:12:15.080 --> 00:12:18.720
<v Speaker 2>but one still exists outside holding a reference to nothing,

247
00:12:19.279 --> 00:12:20.440
<v Speaker 2>a dangling.

248
00:12:20.000 --> 00:12:22.360
<v Speaker 1>Pointer ah, and Rust catches that catches it.

249
00:12:22.320 --> 00:12:24.840
<v Speaker 2>At compile time. It sees that two lives for a

250
00:12:24.840 --> 00:12:27.840
<v Speaker 2>shorter time than one needs the reference, it will refuse

251
00:12:27.879 --> 00:12:31.679
<v Speaker 2>to compile. This is fundamental to Rust's memory safety guarantee.

252
00:12:31.840 --> 00:12:34.320
<v Speaker 1>So how do you handle references across scopes? Then?

253
00:12:34.679 --> 00:12:39.039
<v Speaker 2>Rust allows for explicit lifetime annotations using syntax like a pike.

254
00:12:39.399 --> 00:12:43.240
<v Speaker 2>These essentially let you tell the compiler, Hey, this reference

255
00:12:43.279 --> 00:12:45.519
<v Speaker 2>I'm passing around will only be valid as long as

256
00:12:45.559 --> 00:12:48.039
<v Speaker 2>this other piece of data it points to is valid.

257
00:12:48.679 --> 00:12:51.919
<v Speaker 2>It helps manage complex borrowing scenarios across different parts of

258
00:12:51.960 --> 00:12:52.480
<v Speaker 2>your code.

259
00:12:52.559 --> 00:12:56.759
<v Speaker 1>Okay, so Rust has these powerful, strict foundational principles, but

260
00:12:56.879 --> 00:12:59.480
<v Speaker 1>how do you actually build something with it? What are

261
00:12:59.480 --> 00:13:02.759
<v Speaker 1>the tools and architectural blueprints that make this robust, high

262
00:13:02.799 --> 00:13:06.200
<v Speaker 1>performance code come to life? In Python? We have PIP

263
00:13:06.240 --> 00:13:09.480
<v Speaker 1>and virtual and PIV. How does Rust handle its projects?

264
00:13:09.720 --> 00:13:12.720
<v Speaker 2>For Rust, Cargo is your single source of truth. It's

265
00:13:12.720 --> 00:13:15.519
<v Speaker 2>the all in one tool that handles everything everything pretty

266
00:13:15.600 --> 00:13:19.840
<v Speaker 2>much running your code, testing, generating documentation, building your binaries,

267
00:13:19.879 --> 00:13:23.519
<v Speaker 2>managing your dependencies from creates dot io the Rust package registry.

268
00:13:23.559 --> 00:13:25.200
<v Speaker 2>It's incredibly integrated.

269
00:13:24.759 --> 00:13:25.679
<v Speaker 1>And project setup.

270
00:13:25.919 --> 00:13:29.120
<v Speaker 2>Your project configuration is neatly defined in a single Cargo

271
00:13:29.159 --> 00:13:32.879
<v Speaker 2>dot tomol file like Python's pipeproject dot tomlol or set

272
00:13:32.960 --> 00:13:36.679
<v Speaker 2>up dot py, but maybe more standardized. It specifies things

273
00:13:36.720 --> 00:13:39.480
<v Speaker 2>like the package name, version, authors, dependencies.

274
00:13:39.519 --> 00:13:42.559
<v Speaker 1>What about ignoring build files like Python's pi cash.

275
00:13:42.679 --> 00:13:46.080
<v Speaker 2>What's wonderfully clean about Rust is its dot get ignore file.

276
00:13:46.320 --> 00:13:49.600
<v Speaker 2>Typically it just contains target. That's it. Target. Yeah, because

277
00:13:49.639 --> 00:13:55.519
<v Speaker 2>everything Cargo produces compile binaries, documentation Cach's intermediate files is

278
00:13:55.600 --> 00:13:59.039
<v Speaker 2>neatly stored in that single directory. It keeps your project

279
00:13:59.120 --> 00:14:02.919
<v Speaker 2>root very clean compared to Python's often sprawling cash files

280
00:14:03.200 --> 00:14:04.240
<v Speaker 2>and build artifacts.

281
00:14:04.399 --> 00:14:07.440
<v Speaker 1>That sounds nice and I understand. Cargo also has integrated

282
00:14:07.480 --> 00:14:09.360
<v Speaker 1>documentation capabilities.

283
00:14:09.440 --> 00:14:13.200
<v Speaker 2>Yes, it's fantastic. You just run carbo doc. It generates interactive,

284
00:14:13.399 --> 00:14:17.360
<v Speaker 2>searchable mark down based documentation directly from your code comments

285
00:14:17.480 --> 00:14:19.759
<v Speaker 2>using for docs, much like Python.

286
00:14:19.399 --> 00:14:20.960
<v Speaker 1>Dock strengths, and you view it locally.

287
00:14:21.120 --> 00:14:23.879
<v Speaker 2>Yep opens right in your web browser. It's a testament

288
00:14:23.879 --> 00:14:27.240
<v Speaker 2>to Rust's philosophy of encouraging good practices right from the start.

289
00:14:27.679 --> 00:14:30.120
<v Speaker 2>Documenting public APIs is strongly.

290
00:14:29.759 --> 00:14:33.759
<v Speaker 1>Encouraged beyond documentation, How do you design your Rust modules

291
00:14:33.759 --> 00:14:37.320
<v Speaker 1>for scalability while maintaining that strictness. Rust is known for

292
00:14:37.440 --> 00:14:40.080
<v Speaker 1>effectively enforcing your application's architecture.

293
00:14:40.480 --> 00:14:43.840
<v Speaker 2>The PUB keyword in Rust is key. By default, everything

294
00:14:43.879 --> 00:14:47.759
<v Speaker 2>is private to its module. You use pub to make items, functions, structs,

295
00:14:47.919 --> 00:14:49.879
<v Speaker 2>enoms public for use outside their.

296
00:14:49.759 --> 00:14:52.159
<v Speaker 1>Module opt invisibility exactly.

297
00:14:52.639 --> 00:14:55.360
<v Speaker 2>The core principle here is to isolate modules to a

298
00:14:55.480 --> 00:15:00.840
<v Speaker 2>single cohesive concept. This drastically increases flexibility and makes large

299
00:15:00.840 --> 00:15:03.480
<v Speaker 2>applications far easier to maintain and understand.

300
00:15:03.559 --> 00:15:05.080
<v Speaker 1>And Rust helps enforce this.

301
00:15:05.320 --> 00:15:08.440
<v Speaker 2>Oh yeah, Rust doesn't just suggest this, it enforces it.

302
00:15:08.840 --> 00:15:12.360
<v Speaker 2>Rust actively won't compile if you try to access non

303
00:15:12.360 --> 00:15:16.840
<v Speaker 2>public implementation details from outside a module. This forces you

304
00:15:16.879 --> 00:15:21.000
<v Speaker 2>to define clear well structured public interfaces.

305
00:15:20.480 --> 00:15:22.879
<v Speaker 1>So you have to think about your module boundaries.

306
00:15:22.399 --> 00:15:25.399
<v Speaker 2>You really do. For instance, in a module handling, say

307
00:15:26.000 --> 00:15:29.480
<v Speaker 2>stock orders, you would define public enoms like order type,

308
00:15:29.600 --> 00:15:32.679
<v Speaker 2>public structs like order, and then provide public functions maybe

309
00:15:32.679 --> 00:15:35.679
<v Speaker 2>constructors like order dot new and methods like order dot

310
00:15:35.720 --> 00:15:36.679
<v Speaker 2>current value.

311
00:15:36.440 --> 00:15:39.039
<v Speaker 1>And keep the internal workings private precisely.

312
00:15:39.559 --> 00:15:42.080
<v Speaker 2>You can even use option F thirty two for optional

313
00:15:42.120 --> 00:15:45.399
<v Speaker 2>parameters in your public functions and using self for methods

314
00:15:45.399 --> 00:15:48.000
<v Speaker 2>that just read data rather than self, which takes ownership,

315
00:15:48.320 --> 00:15:52.279
<v Speaker 2>is important for usability. This layered approach means internal refactoring

316
00:15:52.320 --> 00:15:55.039
<v Speaker 2>is safer as long as the public interface stays the same.

317
00:15:55.240 --> 00:15:56.639
<v Speaker 2>You don't break downstream code.

318
00:15:57.000 --> 00:15:59.679
<v Speaker 1>So we've got to handle on Rust's building blocks. Now,

319
00:16:00.000 --> 00:16:02.159
<v Speaker 1>if you take this compiled rustcode and actually combine it

320
00:16:02.200 --> 00:16:06.200
<v Speaker 1>with Python's existing distribution methods, what's involved in packaging and

321
00:16:06.240 --> 00:16:08.799
<v Speaker 1>distributing rustcode as a Python pip module.

322
00:16:08.960 --> 00:16:11.840
<v Speaker 2>Okay, so, for packaging standard Python pitp modules, you typically

323
00:16:11.879 --> 00:16:14.720
<v Speaker 2>start with a GitHub repository, use a dot godn or

324
00:16:14.840 --> 00:16:18.200
<v Speaker 2>file again usually much smaller for Rust. Often just target

325
00:16:18.279 --> 00:16:20.159
<v Speaker 2>within the Rust part and then can figure a setup

326
00:16:20.159 --> 00:16:24.080
<v Speaker 2>dot pifile or more modernly piproject dot tom l with

327
00:16:24.159 --> 00:16:25.000
<v Speaker 2>a build back end.

328
00:16:25.120 --> 00:16:26.919
<v Speaker 1>Right the usual Python packaging stuff.

329
00:16:26.960 --> 00:16:30.519
<v Speaker 2>Yeah, this defines all your package metadata like the name, version, authors,

330
00:16:30.559 --> 00:16:34.519
<v Speaker 2>and crucial dependencies via install requires installing. It is then

331
00:16:34.559 --> 00:16:38.159
<v Speaker 2>as simple as pip installed, get plus https dot GitHub

332
00:16:38.200 --> 00:16:40.919
<v Speaker 2>dot com, your dash repoy, your dat package at main

333
00:16:41.360 --> 00:16:43.679
<v Speaker 2>or eventually pick install your package from PIPI.

334
00:16:44.080 --> 00:16:45.799
<v Speaker 1>Any security things to watch out for.

335
00:16:45.799 --> 00:16:49.600
<v Speaker 2>There definitely a quick but important note. Traditionally, set up

336
00:16:49.679 --> 00:16:53.039
<v Speaker 2>dot pi runs arbitrary code as your user potentially root

337
00:16:53.159 --> 00:16:56.759
<v Speaker 2>during installation. Malicious code injected there can be a serious

338
00:16:56.879 --> 00:17:00.399
<v Speaker 2>vector for attack. Modern builds with piproject dot com tomol

339
00:17:00.480 --> 00:17:03.519
<v Speaker 2>can mitigate this somewhat. Also, always be wary of type

340
00:17:03.519 --> 00:17:07.079
<v Speaker 2>A squadding accidentally installing Request instead of the popular Request

341
00:17:07.119 --> 00:17:08.079
<v Speaker 2>library for example.

342
00:17:08.200 --> 00:17:11.279
<v Speaker 1>Good points. And if you want to expose a command

343
00:17:11.319 --> 00:17:14.039
<v Speaker 1>line airphase from your Python package so users can run

344
00:17:14.039 --> 00:17:18.240
<v Speaker 1>scripts directly from the terminal like my tool inputfile dot txt.

345
00:17:18.160 --> 00:17:20.960
<v Speaker 2>You'd use Python's built in argparse module for parsing those

346
00:17:20.960 --> 00:17:23.559
<v Speaker 2>command line arguments and then define entry points in your

347
00:17:23.559 --> 00:17:26.599
<v Speaker 2>setup dot PI or piproject dot tomol. This links a

348
00:17:26.640 --> 00:17:29.960
<v Speaker 2>console command, say fib number, directly to a specific Python

349
00:17:30.000 --> 00:17:31.200
<v Speaker 2>function within your package.

350
00:17:31.240 --> 00:17:34.640
<v Speaker 1>Okay, what about quality control like unit testing? How do

351
00:17:34.720 --> 00:17:37.759
<v Speaker 1>we ensure the Python side of our fused application is robust?

352
00:17:37.960 --> 00:17:41.599
<v Speaker 2>For Python unit testing, the standard library's unitist module is common,

353
00:17:41.880 --> 00:17:44.680
<v Speaker 2>or frameworks like bytest. With unit test, you inherit from

354
00:17:44.680 --> 00:17:48.000
<v Speaker 2>test case your test simply functions prefixed with test.

355
00:17:47.920 --> 00:17:49.839
<v Speaker 1>And you use ascertain methods.

356
00:17:49.880 --> 00:17:53.359
<v Speaker 2>YEP assertion methods like assert equal, assert true, assert reasons

357
00:17:53.400 --> 00:17:57.519
<v Speaker 2>for checks. For more advanced scenarios, especially when dealing with

358
00:17:57.559 --> 00:18:00.599
<v Speaker 2>external dependencies or complex logic you want to.

359
00:18:00.559 --> 00:18:03.480
<v Speaker 1>Isolate, like network calls or database access.

360
00:18:03.119 --> 00:18:07.599
<v Speaker 2>Exactly, patch from unit test dot mock is invaluable. You

361
00:18:07.599 --> 00:18:10.079
<v Speaker 2>can use it as a decorator to temporarily replace or

362
00:18:10.160 --> 00:18:13.480
<v Speaker 2>mock dependent functions or objects. This allows you to test

363
00:18:13.480 --> 00:18:17.839
<v Speaker 2>specific logic in isolation and even inspect like what arguments

364
00:18:17.880 --> 00:18:19.319
<v Speaker 2>were passed to your mocked functions.

365
00:18:19.400 --> 00:18:21.720
<v Speaker 1>Very useful for isolating units. How do you run them?

366
00:18:21.839 --> 00:18:24.640
<v Speaker 2>You can automate running these tests with a simple batchscript

367
00:18:25.000 --> 00:18:27.759
<v Speaker 2>or use tools like tox or just run Python basem,

368
00:18:27.880 --> 00:18:29.759
<v Speaker 2>unit test, discover or PI test.

369
00:18:29.880 --> 00:18:33.079
<v Speaker 1>And for continuous integrasion, automating all this testing and maybe

370
00:18:33.079 --> 00:18:35.400
<v Speaker 1>deployment on platforms like GitHub.

371
00:18:35.000 --> 00:18:37.799
<v Speaker 2>Gehub actions are perfect for this. You automate these processes

372
00:18:37.920 --> 00:18:41.440
<v Speaker 2>defined in dot iml files like run tests, dot aml

373
00:18:41.720 --> 00:18:44.039
<v Speaker 2>and your dot GitHub workflows directory.

374
00:18:43.720 --> 00:18:46.000
<v Speaker 1>And trigger them on pushes or poor requests.

375
00:18:46.400 --> 00:18:49.400
<v Speaker 2>Right. The typical steps involve checking out the code, setting

376
00:18:49.480 --> 00:18:53.640
<v Speaker 2>up Python, installing dependencies from requirements dot txt or your

377
00:18:53.640 --> 00:18:57.680
<v Speaker 2>package definition, running your unit tests, and maybe also performing

378
00:18:57.680 --> 00:18:59.559
<v Speaker 2>type checking with tools like mypie.

379
00:19:00.039 --> 00:19:03.240
<v Speaker 1>Can you handle optional features and packages like install extra

380
00:19:03.240 --> 00:19:04.240
<v Speaker 1>stuff only if needed.

381
00:19:04.359 --> 00:19:07.839
<v Speaker 2>Yes, you can define optional dependencies using extras require and

382
00:19:07.880 --> 00:19:11.359
<v Speaker 2>setup dot pie or the equivalent in piproject dot com ol.

383
00:19:11.839 --> 00:19:14.720
<v Speaker 2>This allows users to install, say pip install, flitt and

384
00:19:14.759 --> 00:19:19.240
<v Speaker 2>fibpie server to get specific server related functionalities without bloating

385
00:19:19.240 --> 00:19:20.680
<v Speaker 2>the base install for everyone.

386
00:19:21.000 --> 00:19:23.480
<v Speaker 1>Speak in my pie. How does Python time checking with

387
00:19:23.519 --> 00:19:27.240
<v Speaker 1>my pie compare to Rust's built in aggressive type enforcement

388
00:19:27.279 --> 00:19:28.200
<v Speaker 1>we talked about earlier.

389
00:19:28.279 --> 00:19:31.000
<v Speaker 2>It's a great question. My pie checks type consistency and

390
00:19:31.039 --> 00:19:35.200
<v Speaker 2>Python coming statically. It mimics some of Rust's compile time checks,

391
00:19:35.240 --> 00:19:38.359
<v Speaker 2>catching type errors before you run. But here's the crucial difference.

392
00:19:38.759 --> 00:19:42.039
<v Speaker 2>It's not enforced during run time by the Python interpreter itself, so.

393
00:19:42.079 --> 00:19:45.759
<v Speaker 1>Python might still run code mypieflags.

394
00:19:45.160 --> 00:19:49.279
<v Speaker 2>Potentially, Yes, if the specific type inconsistency doesn't cause an

395
00:19:49.279 --> 00:19:54.279
<v Speaker 2>immediate crash. Mypile provides a fantastic layer of confidence and

396
00:19:54.319 --> 00:19:57.720
<v Speaker 2>catches mini bugs early. But it's a lner an external

397
00:19:57.720 --> 00:20:02.920
<v Speaker 2>tool that you runs. Hope system is a fundamental inescapable

398
00:20:02.920 --> 00:20:05.079
<v Speaker 2>part of its compilation process right.

399
00:20:05.039 --> 00:20:09.200
<v Speaker 1>Compile versalint And finally, a brief mention on automatic versioning.

400
00:20:09.279 --> 00:20:10.279
<v Speaker 1>How can that help you?

401
00:20:10.319 --> 00:20:13.279
<v Speaker 2>Can write Python code, perhaps using the request library to

402
00:20:13.279 --> 00:20:17.039
<v Speaker 2>interact with the PIPIAPI to fetch the latest version number

403
00:20:17.079 --> 00:20:20.880
<v Speaker 2>of your own package or dependency. This is incredibly useful

404
00:20:20.880 --> 00:20:24.920
<v Speaker 2>for automated release processes, generating change logs, and ensuring your

405
00:20:24.920 --> 00:20:28.839
<v Speaker 2>continuous deployment pipelines are always working with the correct intended versions.

406
00:20:29.000 --> 00:20:31.240
<v Speaker 1>All these pieces are fitting together. This brings us to

407
00:20:31.240 --> 00:20:34.599
<v Speaker 1>the actual fusion point. How do Python and Rust truly

408
00:20:34.680 --> 00:20:37.640
<v Speaker 1>talk to each other? What's the secret? Sauce, the mechanism

409
00:20:37.680 --> 00:20:40.960
<v Speaker 1>for creating a Rust interface that Python can seamlessly call the.

410
00:20:40.920 --> 00:20:44.039
<v Speaker 2>PIO three crate is the star here. It's the enabling technology,

411
00:20:44.200 --> 00:20:47.440
<v Speaker 2>the bridge that allows rustcode to interact directly and quite

412
00:20:47.480 --> 00:20:48.279
<v Speaker 2>easily with Python.

413
00:20:48.359 --> 00:20:50.240
<v Speaker 1>Okay, PIO three. How do you set that up? In

414
00:20:50.279 --> 00:20:50.640
<v Speaker 1>your build?

415
00:20:50.960 --> 00:20:53.160
<v Speaker 2>You typically use set up tools rust or mature and

416
00:20:53.240 --> 00:20:56.440
<v Speaker 2>as your build back end in piproject dot tamil. These

417
00:20:56.480 --> 00:20:59.160
<v Speaker 2>tools manage the compilation of the Rust code within your

418
00:20:59.160 --> 00:21:00.599
<v Speaker 2>Python package build.

419
00:21:00.359 --> 00:21:02.319
<v Speaker 1>Process and in the Russ project itself.

420
00:21:02.400 --> 00:21:05.359
<v Speaker 2>In your RUSS project's cargo dot com l you define

421
00:21:05.400 --> 00:21:09.440
<v Speaker 2>create type c ADM. This tells Cargo to compile your

422
00:21:09.519 --> 00:21:12.960
<v Speaker 2>Rust code as a c style dynamic library that Python

423
00:21:13.000 --> 00:21:14.559
<v Speaker 2>can load and interact with like.

424
00:21:14.559 --> 00:21:16.599
<v Speaker 1>A DL or dot so file exactly.

425
00:21:17.200 --> 00:21:20.440
<v Speaker 2>The build tool then links this compiled RUSS library into

426
00:21:20.480 --> 00:21:23.640
<v Speaker 2>the Python package structure. The most important detail in your

427
00:21:23.680 --> 00:21:26.720
<v Speaker 2>Rust code is simply adding a special marker an attribute

428
00:21:26.720 --> 00:21:29.599
<v Speaker 2>macro hashtag PI function on any Rust function you want

429
00:21:29.640 --> 00:21:30.599
<v Speaker 2>to expose to Python.

430
00:21:30.799 --> 00:21:32.440
<v Speaker 1>Just hashtag p function pretty much.

431
00:21:32.920 --> 00:21:35.519
<v Speaker 2>You also need a hashtag PI module function to declare

432
00:21:35.559 --> 00:21:38.880
<v Speaker 2>the Python module itself and add your halftag PI functions

433
00:21:38.920 --> 00:21:41.759
<v Speaker 2>to it. PIO three handles all the complex glue code

434
00:21:41.799 --> 00:21:45.799
<v Speaker 2>type conversions, air handling, mapping in the background. It makes

435
00:21:45.839 --> 00:21:48.440
<v Speaker 2>it feel remarkably like Python is calling just another Python

436
00:21:48.480 --> 00:21:51.440
<v Speaker 2>function even though it's running compiled rustcode under the hood.

437
00:21:51.599 --> 00:21:55.240
<v Speaker 1>So PIO three handles the deep technical plumbing. What about

438
00:21:55.240 --> 00:21:58.480
<v Speaker 1>the Python side of this bridge. We often hear about

439
00:21:58.519 --> 00:22:02.599
<v Speaker 1>adapters and software design. How do Python adapters help bridge

440
00:22:02.599 --> 00:22:05.920
<v Speaker 1>this language gap, especially when bringing in something like Rust.

441
00:22:06.359 --> 00:22:09.559
<v Speaker 2>That's a great pattern to consider. An adapter in general

442
00:22:09.640 --> 00:22:12.359
<v Speaker 2>is a design pattern that manages the interface between two

443
00:22:12.359 --> 00:22:16.039
<v Speaker 2>different modules, applications, or even languages like we're doing here.

444
00:22:16.440 --> 00:22:19.519
<v Speaker 2>Think of it like a universal travel adapter for your electronics.

445
00:22:19.640 --> 00:22:22.400
<v Speaker 1>Right, let's your UK plug work in the US exactly.

446
00:22:22.519 --> 00:22:24.920
<v Speaker 2>It allows your Python code to interact with the Rust

447
00:22:24.960 --> 00:22:28.519
<v Speaker 2>module to a consistent Python interface, even if the underlying

448
00:22:28.559 --> 00:22:32.599
<v Speaker 2>Rust implementation details change later. It provides a flexible layer,

449
00:22:33.240 --> 00:22:36.039
<v Speaker 2>so if your back end computation module switches from say

450
00:22:36.240 --> 00:22:37.720
<v Speaker 2>pure Python to Rust.

451
00:22:37.640 --> 00:22:39.279
<v Speaker 1>You just update the adapter, right.

452
00:22:39.519 --> 00:22:41.519
<v Speaker 2>You only need to update the adapter class to call

453
00:22:41.559 --> 00:22:44.519
<v Speaker 2>the new Rust functions via PIO three, not every single

454
00:22:44.559 --> 00:22:47.599
<v Speaker 2>place in your Python toebase that needed that calculation. This

455
00:22:47.680 --> 00:22:50.079
<v Speaker 2>significantly simplifies maintenance and refactoring.

456
00:22:50.319 --> 00:22:53.720
<v Speaker 1>That makes sense. What about sharing resources, like if multiple

457
00:22:53.759 --> 00:22:56.680
<v Speaker 1>parts of your Python app need to use the same

458
00:22:57.200 --> 00:22:59.039
<v Speaker 1>instance of a Rust backed object.

459
00:22:59.200 --> 00:23:03.680
<v Speaker 2>Ah yeah. A common pattern often used with adapters, particularly

460
00:23:03.680 --> 00:23:06.759
<v Speaker 2>for managing a shared resource or state, is the singleton

461
00:23:06.799 --> 00:23:10.799
<v Speaker 2>design pattern. This ensures that all references to a particular

462
00:23:10.839 --> 00:23:14.240
<v Speaker 2>class point to one single instance of that class throughout

463
00:23:14.279 --> 00:23:14.920
<v Speaker 2>your application.

464
00:23:15.000 --> 00:23:16.160
<v Speaker 1>How do you do that? In Python?

465
00:23:16.559 --> 00:23:19.160
<v Speaker 2>A typical way is using a metaclass, a class that

466
00:23:19.200 --> 00:23:22.160
<v Speaker 2>defines how other classes are created. The meta class can

467
00:23:22.240 --> 00:23:25.319
<v Speaker 2>manage a dictionary of instances, ensuring only one instance of

468
00:23:25.319 --> 00:23:28.000
<v Speaker 2>the target class is ever created and returned. So every

469
00:23:28.000 --> 00:23:29.680
<v Speaker 2>time you ask for the adapter, you get the same

470
00:23:29.720 --> 00:23:30.039
<v Speaker 2>one and.

471
00:23:30.039 --> 00:23:32.519
<v Speaker 1>When you put it all together, especially for raw performance,

472
00:23:32.559 --> 00:23:35.799
<v Speaker 1>what's the big reveal? Like if you're calculating something computationally

473
00:23:35.839 --> 00:23:39.960
<v Speaker 1>intensive like Fibonacci numbers, how does Rust even with PIO

474
00:23:40.000 --> 00:23:43.880
<v Speaker 1>three bindings compared to pure Python or even number Numba

475
00:23:43.920 --> 00:23:45.680
<v Speaker 1>tries to speed up Python two right.

476
00:23:45.839 --> 00:23:49.200
<v Speaker 2>Right Number is a just in time compiler for Python functions,

477
00:23:49.559 --> 00:23:53.559
<v Speaker 2>often using LLVM. It can give significant speed ups. But

478
00:23:53.680 --> 00:23:55.400
<v Speaker 2>this is where the jaws drop when you bring in

479
00:23:55.480 --> 00:23:58.720
<v Speaker 2>Rust via PIO three. We ran a speed test for

480
00:23:58.759 --> 00:24:02.240
<v Speaker 2>fibonacci calculation across pure Python, number and our new Rust

481
00:24:02.279 --> 00:24:05.559
<v Speaker 2>module via PIO three bindings, and the results The results

482
00:24:05.559 --> 00:24:08.920
<v Speaker 2>were astounding. The Rust class, even with the overhead of

483
00:24:08.920 --> 00:24:12.079
<v Speaker 2>those PIO three bindings, translating between Python and RUSS types

484
00:24:12.359 --> 00:24:15.000
<v Speaker 2>was a staggering forty three times faster than the pure

485
00:24:15.039 --> 00:24:18.839
<v Speaker 2>Python class for calculating, say, the thirty fifth Vivonacci number

486
00:24:18.880 --> 00:24:22.039
<v Speaker 2>forty three times forty three. While Number also provided a

487
00:24:22.079 --> 00:24:24.759
<v Speaker 2>significant speed up, maybe ten to fifteen times faster than

488
00:24:24.759 --> 00:24:27.799
<v Speaker 2>pure Python in our test, it comes with its own challenges.

489
00:24:28.240 --> 00:24:30.839
<v Speaker 2>It can be notoriously difficult to install and get working

490
00:24:30.880 --> 00:24:34.039
<v Speaker 2>reliably on certain systems. Like say in M one MAC,

491
00:24:34.359 --> 00:24:37.200
<v Speaker 2>often requiring specific and pilot setups or environments.

492
00:24:37.279 --> 00:24:40.680
<v Speaker 1>So RUSS was significantly faster even than number and potentially

493
00:24:40.720 --> 00:24:43.720
<v Speaker 1>easier to integrate across platforms via PIO three.

494
00:24:43.599 --> 00:24:46.680
<v Speaker 2>In this specific benchmark. Yes, this year, magnitude of Russ's

495
00:24:46.680 --> 00:24:50.599
<v Speaker 2>performance game, even with the interoperability layer, clearly justifies the

496
00:24:50.640 --> 00:24:52.960
<v Speaker 2>integration effort for those critical bottlenecks.

497
00:24:53.079 --> 00:24:57.079
<v Speaker 1>That's a staggering difference. Forty three times faster. Okay, moving

498
00:24:57.119 --> 00:24:59.960
<v Speaker 1>on from raw sequential speed. When you're dealing with complex systems,

499
00:25:00.200 --> 00:25:04.279
<v Speaker 1>mastering concurrency with threads and processes becomes crucial. What's the

500
00:25:04.279 --> 00:25:07.720
<v Speaker 1>fundamental difference between threads and processes? Especially for a Python

501
00:25:07.759 --> 00:25:10.480
<v Speaker 1>developer dealing with the GIL, that's a key distinction.

502
00:25:10.880 --> 00:25:13.640
<v Speaker 2>Threads are the smallest unit of computation. They live within

503
00:25:13.680 --> 00:25:17.960
<v Speaker 2>a single process and share resources like memory and processing power. However,

504
00:25:18.319 --> 00:25:21.079
<v Speaker 2>in Standard Sea Python, due to the Global Interpreter Lock

505
00:25:21.240 --> 00:25:22.440
<v Speaker 2>or GIL.

506
00:25:22.200 --> 00:25:24.079
<v Speaker 1>The infamous GIL.

507
00:25:23.720 --> 00:25:29.480
<v Speaker 2>Indeed, CPU intensive multi threaded tasks often don't speed up significantly,

508
00:25:29.559 --> 00:25:34.079
<v Speaker 2>sometimes even slow down. The GIL essentially prevents true parallel

509
00:25:34.119 --> 00:25:38.279
<v Speaker 2>execution of Python bycode across multiple threads on multiple CPU

510
00:25:38.319 --> 00:25:39.680
<v Speaker 2>core simultaneously, So.

511
00:25:39.599 --> 00:25:41.359
<v Speaker 1>Python, threads are better for.

512
00:25:41.759 --> 00:25:45.000
<v Speaker 2>What they're primarily useful for IO heavy tasks, things like

513
00:25:45.039 --> 00:25:48.839
<v Speaker 2>waiting for network responses, reading writing files. While one thread

514
00:25:48.880 --> 00:25:51.640
<v Speaker 2>is waiting, the GIL can be released, allowing another thread

515
00:25:51.640 --> 00:25:55.440
<v Speaker 2>to run, so you get concurrency but not necessarily.

516
00:25:54.880 --> 00:25:57.000
<v Speaker 1>CPU parallelism okay, and processes.

517
00:25:57.119 --> 00:25:59.640
<v Speaker 2>Processes, on the other hand, are more expensive to create

518
00:25:59.720 --> 00:26:02.720
<v Speaker 2>than three threads because they are independent entities managed by

519
00:26:02.759 --> 00:26:06.359
<v Speaker 2>the operating system. They do not share memory by default,

520
00:26:06.839 --> 00:26:10.359
<v Speaker 2>which makes them inherently safer for true parallelism, as each

521
00:26:10.400 --> 00:26:14.680
<v Speaker 2>process gets its own Python interpreter and its own GIL.

522
00:26:13.920 --> 00:26:16.160
<v Speaker 1>So they can run on multiple cores properly.

523
00:26:16.720 --> 00:26:20.400
<v Speaker 2>Yes, a process can host multiple threads itself, but the

524
00:26:20.480 --> 00:26:24.880
<v Speaker 2>key is that different processes run independently. While context switches

525
00:26:24.960 --> 00:26:28.799
<v Speaker 2>between processes are more expensive for the OS, they're generally

526
00:26:28.839 --> 00:26:32.559
<v Speaker 2>better for both CPU bound and ioheavy tasks in Python

527
00:26:33.039 --> 00:26:36.000
<v Speaker 2>because they bypass the GIL limitation for parallelism.

528
00:26:36.119 --> 00:26:38.640
<v Speaker 1>So how do you practically spin up these concurrent workers?

529
00:26:38.680 --> 00:26:41.920
<v Speaker 1>Starting with Python threads. If you're doing iobound work.

530
00:26:41.839 --> 00:26:45.039
<v Speaker 2>In Python, you use the Standard Library's threading thread class.

531
00:26:45.279 --> 00:26:47.400
<v Speaker 2>You create a thread, give it a target function, and

532
00:26:47.440 --> 00:26:50.200
<v Speaker 2>call start. The join method is critical it blocks the

533
00:26:50.200 --> 00:26:53.000
<v Speaker 2>main program until that specific thread finishes its.

534
00:26:52.880 --> 00:26:55.359
<v Speaker 1>Execution, and they finish in any order. Right.

535
00:26:55.720 --> 00:26:58.319
<v Speaker 2>It's important to remember that when you spawn multiple threads,

536
00:26:58.519 --> 00:27:02.359
<v Speaker 2>they generally finish in determined order whenever their task completes.

537
00:27:02.720 --> 00:27:06.160
<v Speaker 1>How does Rust handle threads? Can it use multiple cores?

538
00:27:06.440 --> 00:27:10.799
<v Speaker 2>Yes? Absolutely? In Rust you use std dot thread dot spawn,

539
00:27:10.960 --> 00:27:13.400
<v Speaker 2>which takes a closure to run. It returns a join handle.

540
00:27:13.839 --> 00:27:17.400
<v Speaker 2>Unlike Python's gil limited threads, threads in Rust can fully

541
00:27:17.480 --> 00:27:21.440
<v Speaker 2>utilize multiple CPU cores for true parallel execution if the

542
00:27:21.480 --> 00:27:22.000
<v Speaker 2>hardware is.

543
00:27:21.960 --> 00:27:25.079
<v Speaker 1>Available, so real parallelism real parallelism.

544
00:27:25.480 --> 00:27:28.359
<v Speaker 2>The join function on the joint handle in Rust returns

545
00:27:28.359 --> 00:27:32.119
<v Speaker 2>a result, specifically something like result a boxed in any

546
00:27:32.119 --> 00:27:34.640
<v Speaker 2>plus end, where t is the return type of your

547
00:27:34.640 --> 00:27:35.839
<v Speaker 2>threads closure.

548
00:27:35.480 --> 00:27:38.599
<v Speaker 1>WHOA break that down result box tin any plus send okay.

549
00:27:38.680 --> 00:27:42.519
<v Speaker 2>Result is the standard ok or air for error handling.

550
00:27:42.960 --> 00:27:46.960
<v Speaker 2>If the thread panics crashes. Join returns in air. Box

551
00:27:47.039 --> 00:27:48.920
<v Speaker 2>means the error is allocated on the heat because its

552
00:27:48.920 --> 00:27:51.400
<v Speaker 2>size might not be known at compile time. Any means

553
00:27:51.400 --> 00:27:53.880
<v Speaker 2>the error could be dynamically of almost any type, and

554
00:27:54.000 --> 00:27:56.119
<v Speaker 2>send is a marker trait indicating the error type is

555
00:27:56.160 --> 00:27:58.400
<v Speaker 2>safe to transfer across thread boundaries.

556
00:27:58.000 --> 00:28:00.759
<v Speaker 1>So rust makes thread errors type safe very.

557
00:28:00.640 --> 00:28:03.119
<v Speaker 2>Much, so it forces you to handle that potential error

558
00:28:03.200 --> 00:28:05.599
<v Speaker 2>or panic from the join thread in showing robustness.

559
00:28:05.799 --> 00:28:09.200
<v Speaker 1>And what about managing multiple processes in Python, especially for

560
00:28:09.279 --> 00:28:12.559
<v Speaker 1>those CPU bound tasks where you want to bypass the GIL.

561
00:28:12.839 --> 00:28:17.839
<v Speaker 2>Python's multiprocessing pool is an excellent abstraction for this. A

562
00:28:17.920 --> 00:28:21.359
<v Speaker 2>pool manages a fixed number of worker processes. You can

563
00:28:21.400 --> 00:28:24.039
<v Speaker 2>easily map a function over a list of inputs, and

564
00:28:24.079 --> 00:28:27.359
<v Speaker 2>the pool distributes the work across its processes, collecting the

565
00:28:27.400 --> 00:28:29.359
<v Speaker 2>results back into an array or a list for you.

566
00:28:29.720 --> 00:28:32.039
<v Speaker 1>So it handles the process creation and communication.

567
00:28:32.400 --> 00:28:35.119
<v Speaker 2>Yeah, it abstracts away a lot of the boilerplate. This

568
00:28:35.200 --> 00:28:39.680
<v Speaker 2>approach keeps the expense of multiprocessing context localized and allows

569
00:28:39.680 --> 00:28:42.519
<v Speaker 2>for easy control over the number of workers, which is

570
00:28:42.599 --> 00:28:47.400
<v Speaker 2>great for parallelizing heavy computations without managing individual processes manually.

571
00:28:47.839 --> 00:28:51.599
<v Speaker 1>We often use Fibonacci recursion as a classic example in programming,

572
00:28:51.680 --> 00:28:54.680
<v Speaker 1>but there's a warning about it in the context of concurrency. Right,

573
00:28:54.960 --> 00:28:56.599
<v Speaker 1>it's not always a great benchmark.

574
00:28:56.799 --> 00:29:00.920
<v Speaker 2>Yes, absolutely be careful with recursive Fibonac. It leads to

575
00:29:00.960 --> 00:29:05.480
<v Speaker 2>an exponential computation tree the same calculations are repeated countless times.

576
00:29:05.559 --> 00:29:07.359
<v Speaker 2>It's incredibly inefficient.

577
00:29:06.960 --> 00:29:10.279
<v Speaker 1>So it looks busy, but isn't doing much unique work exactly.

578
00:29:10.440 --> 00:29:13.200
<v Speaker 2>If you're using it to demonstrate concurrency, be aware that

579
00:29:13.240 --> 00:29:16.880
<v Speaker 2>the overhead of spinning up processes or even threads can

580
00:29:16.960 --> 00:29:21.000
<v Speaker 2>easily outweigh any computational gains unless the number you're calculating

581
00:29:21.079 --> 00:29:24.720
<v Speaker 2>is genuinely large, and you use techniques like memoization or

582
00:29:24.799 --> 00:29:29.240
<v Speaker 2>caching to avoid recalculating redundant values. It's often a poor

583
00:29:29.359 --> 00:29:32.839
<v Speaker 2>choice for benchmarking real world performance gains from concurrency.

584
00:29:33.319 --> 00:29:36.839
<v Speaker 1>But if you want to achieve genuine efficient multi threading

585
00:29:36.880 --> 00:29:40.440
<v Speaker 1>and Rust for a real CPU bound task, what's a

586
00:29:40.440 --> 00:29:42.519
<v Speaker 1>good way to do it without all that low level

587
00:29:42.640 --> 00:29:44.039
<v Speaker 1>manual thread management.

588
00:29:44.519 --> 00:29:47.599
<v Speaker 2>The Rayon crate is fantastic for this. It's almost magical.

589
00:29:47.960 --> 00:29:50.680
<v Speaker 2>It simplifies data parallelism in Rust tremendously.

590
00:29:50.759 --> 00:29:51.359
<v Speaker 1>How does it work?

591
00:29:51.440 --> 00:29:54.160
<v Speaker 2>By simply calling dot interperator on a collection like a VEC,

592
00:29:54.599 --> 00:29:57.519
<v Speaker 2>you convert a standard iterator into a parallel iterator. You

593
00:29:57.559 --> 00:30:01.079
<v Speaker 2>can then apply functions like map, filter, induced, etc. Just

594
00:30:01.079 --> 00:30:05.119
<v Speaker 2>like normal iterators, but Rayon automatically parallelizes the work across

595
00:30:05.119 --> 00:30:08.000
<v Speaker 2>a threadpool. Utilizing all available CPU.

596
00:30:07.680 --> 00:30:10.160
<v Speaker 1>Course, so just change perator basically.

597
00:30:10.400 --> 00:30:14.240
<v Speaker 2>Often it's close to that simple for basic data parallelism.

598
00:30:14.480 --> 00:30:18.119
<v Speaker 2>For tasks like parallel FIBONACI calculations on a list of numbers,

599
00:30:18.519 --> 00:30:22.160
<v Speaker 2>this approach can yield significantly better speed increases. We've seen

600
00:30:22.160 --> 00:30:24.440
<v Speaker 2>it yield maybe a twenty percent or more speed up

601
00:30:24.440 --> 00:30:28.759
<v Speaker 2>and RUST for appropriate tasks, compared to Python's multiprocessing, which

602
00:30:28.839 --> 00:30:30.720
<v Speaker 2>might only give you a five percent gain for the

603
00:30:30.720 --> 00:30:34.079
<v Speaker 2>same task, partly due to the GIL and pythons overheads.

604
00:30:34.119 --> 00:30:37.880
<v Speaker 1>Even in multiprocessing, What if you need lower level control

605
00:30:37.920 --> 00:30:40.680
<v Speaker 1>in Rust without Rayon, Well, if you.

606
00:30:40.599 --> 00:30:42.680
<v Speaker 2>Wanted to go even lower level in Rust without third

607
00:30:42.680 --> 00:30:46.319
<v Speaker 2>party creates like Rayon, it involves manually using std dot

608
00:30:46.359 --> 00:30:50.279
<v Speaker 2>process dot command to spawn new OS processes and explicitly

609
00:30:50.319 --> 00:30:53.440
<v Speaker 2>managing their student and stood out pipes, perhaps using tools

610
00:30:53.480 --> 00:30:55.880
<v Speaker 2>like buff reader to send data back and forth. It's

611
00:30:55.960 --> 00:30:57.000
<v Speaker 2>much more manual.

612
00:30:56.720 --> 00:30:59.960
<v Speaker 1>Work, sounds complex. It seems like they're powerful tools for concurrency,

613
00:31:00.119 --> 00:31:03.200
<v Speaker 1>but also some silent killers to watch out for. What

614
00:31:03.240 --> 00:31:05.759
<v Speaker 1>are some of those common concurrency traps that can completely

615
00:31:05.759 --> 00:31:08.880
<v Speaker 1>derail your performance gains or even cause your application to

616
00:31:08.880 --> 00:31:09.720
<v Speaker 1>grind to a Hult.

617
00:31:09.799 --> 00:31:14.039
<v Speaker 2>Yeah, concurrency is powerful, but tricky. First, what's crucial to

618
00:31:14.119 --> 00:31:17.480
<v Speaker 2>understand is Ambel's law. It basically states that there are

619
00:31:17.519 --> 00:31:20.920
<v Speaker 2>diminishing returns when you keep adding more cores or workers

620
00:31:20.960 --> 00:31:21.519
<v Speaker 2>to a task.

621
00:31:21.880 --> 00:31:23.400
<v Speaker 1>More isn't always better.

622
00:31:23.359 --> 00:31:26.680
<v Speaker 2>Not infinitely better. No, if only say fifty percent of

623
00:31:26.680 --> 00:31:30.160
<v Speaker 2>your task can actually be parallelized, even with infinite cores,

624
00:31:30.319 --> 00:31:32.359
<v Speaker 2>you'll only ever get a two x speed up max.

625
00:31:32.920 --> 00:31:36.079
<v Speaker 2>The formula speed up pull one one P plus PS

626
00:31:36.319 --> 00:31:40.079
<v Speaker 2>illustrates this P is the parallelizable proportion. S is the

627
00:31:40.079 --> 00:31:43.359
<v Speaker 2>speed up of that part. Performance will inevitably plateau based

628
00:31:43.359 --> 00:31:46.200
<v Speaker 2>on the sequential portion. So throwing more hardware at a

629
00:31:46.240 --> 00:31:49.240
<v Speaker 2>problem with inherent sequential bottlenecks won't help indefinitely.

630
00:31:49.359 --> 00:31:51.960
<v Speaker 1>Okay, Amidl's law sets limits. What else?

631
00:31:52.079 --> 00:31:55.519
<v Speaker 2>Then? There are deadlocks. These are truly insidious, especially in

632
00:31:55.559 --> 00:31:59.759
<v Speaker 2>distributed systems. They often occur in applications managing multiprocessing via

633
00:31:59.799 --> 00:32:02.759
<v Speaker 2>a task broker or a queue like red as or salary.

634
00:32:02.920 --> 00:32:04.400
<v Speaker 1>How does a deadlock happen there?

635
00:32:04.680 --> 00:32:07.240
<v Speaker 2>Imagine a task running in your worker pool needs a

636
00:32:07.240 --> 00:32:09.599
<v Speaker 2>result of another task to finish, so it sends that

637
00:32:09.640 --> 00:32:12.680
<v Speaker 2>new task dependency to the queue and waits. But what

638
00:32:12.720 --> 00:32:15.039
<v Speaker 2>if the worker pool is already full and maybe all

639
00:32:15.039 --> 00:32:17.279
<v Speaker 2>the busy workers are also waiting for their dependencies.

640
00:32:17.559 --> 00:32:20.279
<v Speaker 1>Ah, so nothing new can start and everything is waiting

641
00:32:20.279 --> 00:32:21.680
<v Speaker 1>for something else that can't start.

642
00:32:22.000 --> 00:32:26.000
<v Speaker 2>Gridlock exactly. And the crucial frustrating part is that no

643
00:32:26.240 --> 00:32:30.359
<v Speaker 2>errors are raised. Usually your system just quietly hangs. Debugging

644
00:32:30.400 --> 00:32:31.680
<v Speaker 2>deadlocks can be a nightmare.

645
00:32:31.799 --> 00:32:33.519
<v Speaker 1>Yikes. Okay, what's the third trap?

646
00:32:33.640 --> 00:32:37.720
<v Speaker 2>Finally? Race conditions. These are probably the most common concurrency bug.

647
00:32:38.319 --> 00:32:41.799
<v Speaker 2>They occur when multiple threads or processes try to access

648
00:32:41.799 --> 00:32:46.759
<v Speaker 2>and modify shared data simultaneously without proper coordination. The final

649
00:32:46.799 --> 00:32:51.960
<v Speaker 2>result depends unpredictably on the exact timing and interleaving of operations.

650
00:32:51.440 --> 00:32:54.119
<v Speaker 1>Leading to corrupted data or inconsistent state.

651
00:32:54.240 --> 00:32:58.039
<v Speaker 2>Precisely, locks, mutexes or semaphors can prevent this, but only

652
00:32:58.079 --> 00:33:01.880
<v Speaker 2>reliably within a single process. For distributed systems or shared

653
00:33:01.920 --> 00:33:05.119
<v Speaker 2>resources like databases or caches, you often rely on the

654
00:33:05.119 --> 00:33:08.960
<v Speaker 2>built in atomic operations or transaction mechanisms those systems provide.

655
00:33:09.039 --> 00:33:10.359
<v Speaker 1>So what's the big message here?

656
00:33:10.519 --> 00:33:14.119
<v Speaker 2>The big message is concurrency is hard. If it becomes

657
00:33:14.119 --> 00:33:17.079
<v Speaker 2>overly complex to manage, or you're spending more time debugging

658
00:33:17.119 --> 00:33:21.279
<v Speaker 2>deadlocks and race conditions than writing features. Then maybe simpler

659
00:33:21.319 --> 00:33:24.240
<v Speaker 2>sequential programming is safer for that part of the system,

660
00:33:24.599 --> 00:33:27.359
<v Speaker 2>or it's a strong sign that a fundamental design rethink

661
00:33:27.400 --> 00:33:31.119
<v Speaker 2>is needed for your architecture. Don't introduce concurrency.

662
00:33:30.680 --> 00:33:34.400
<v Speaker 1>Lightly wise words. Okay, moving on to putting it all

663
00:33:34.440 --> 00:33:37.519
<v Speaker 1>together in a real world scenario, how do you structure

664
00:33:37.519 --> 00:33:41.119
<v Speaker 1>a Python flask gap to seamlessly integrate rust building a

665
00:33:41.119 --> 00:33:44.640
<v Speaker 1>truly robust and high performance foundation. Let's think about a

666
00:33:44.640 --> 00:33:45.200
<v Speaker 1>web out.

667
00:33:45.279 --> 00:33:48.279
<v Speaker 2>Right a common use case, you'd likely build a comprehensive

668
00:33:48.279 --> 00:33:51.640
<v Speaker 2>flask web application stack that might include an NGI and

669
00:33:51.799 --> 00:33:55.880
<v Speaker 2>x reverse proxy for handling incoming requests and serving static files,

670
00:33:56.200 --> 00:33:58.599
<v Speaker 2>a postgressful database for persistence.

671
00:33:58.319 --> 00:34:00.880
<v Speaker 1>A sellery message bus with you redd Us as a

672
00:34:00.880 --> 00:34:02.880
<v Speaker 1>broker for background tasks exactly.

673
00:34:02.920 --> 00:34:05.359
<v Speaker 2>And you'd probably package all of this using Docker containers

674
00:34:05.440 --> 00:34:08.840
<v Speaker 2>orchestrated by Docker Compose for easy development and deployment.

675
00:34:09.079 --> 00:34:12.559
<v Speaker 1>What special setup does the flask apps docker container need

676
00:34:12.599 --> 00:34:13.119
<v Speaker 1>for Rust?

677
00:34:13.480 --> 00:34:16.519
<v Speaker 2>For the Docker file setup for your main class application service,

678
00:34:16.840 --> 00:34:19.360
<v Speaker 2>you'll need to make sure you install the necessary build tools.

679
00:34:19.559 --> 00:34:22.360
<v Speaker 2>That usually means things like Python three dev for Python

680
00:34:22.480 --> 00:34:26.480
<v Speaker 2>c headers, maybe build essential or specifically GCC and the

681
00:34:26.559 --> 00:34:28.960
<v Speaker 2>Rust tool chain itself, so you can compile your Rust

682
00:34:29.000 --> 00:34:32.559
<v Speaker 2>extension module directly within the container image during the Docker build.

683
00:34:32.960 --> 00:34:36.440
<v Speaker 1>Got it install Rust and build tools in the docker file.

684
00:34:37.079 --> 00:34:40.760
<v Speaker 1>What about the Python code structure database access.

685
00:34:40.679 --> 00:34:43.400
<v Speaker 2>For your database access layer in Python, you'd likely use

686
00:34:43.440 --> 00:34:46.519
<v Speaker 2>squalk me as an arm and a lembic for managing

687
00:34:46.639 --> 00:34:50.840
<v Speaker 2>database migrations, tracking and applying schema changes reliably over time.

688
00:34:51.599 --> 00:34:54.599
<v Speaker 2>Psychot two binary is the common driver for connecting squalkam

689
00:34:54.679 --> 00:34:55.320
<v Speaker 2>to postgress.

690
00:34:55.880 --> 00:34:58.320
<v Speaker 1>Any tips for managing the database connection itself.

691
00:34:58.679 --> 00:35:02.320
<v Speaker 2>Yes, Crucially, it's good practice to initialize your database engine

692
00:35:02.559 --> 00:35:06.199
<v Speaker 2>dB engine using squalkmmuse creen engine only once, perhaps as

693
00:35:06.199 --> 00:35:09.440
<v Speaker 2>a singleton instance, and import that instance across your application.

694
00:35:09.960 --> 00:35:12.880
<v Speaker 2>This helps avoid issues like too many dangling sessions or

695
00:35:12.920 --> 00:35:14.360
<v Speaker 2>connection fool exhaustion.

696
00:35:14.119 --> 00:35:15.679
<v Speaker 1>And cleaning up sessions right.

697
00:35:16.039 --> 00:35:19.840
<v Speaker 2>Database sessions must also be properly managed per request. You

698
00:35:19.920 --> 00:35:22.159
<v Speaker 2>typically create a session at the start of a request

699
00:35:22.440 --> 00:35:26.199
<v Speaker 2>and insure it's expired, closed and removed using flasks at

700
00:35:26.199 --> 00:35:29.320
<v Speaker 2>app dot teardown request or at app dot teardown app

701
00:35:29.360 --> 00:35:32.760
<v Speaker 2>context decorators after each request finishes or fails.

702
00:35:32.519 --> 00:35:34.920
<v Speaker 1>And integrating sealery for background tasks.

703
00:35:34.719 --> 00:35:38.400
<v Speaker 2>Celery integrates nicely with the flask app context. You usually

704
00:35:38.480 --> 00:35:42.199
<v Speaker 2>use a Makecelery factory function. This allows your background Selery

705
00:35:42.280 --> 00:35:46.159
<v Speaker 2>tasks to access your flask apps configuration and extensions like

706
00:35:46.199 --> 00:35:50.079
<v Speaker 2>the database session correctly. It lets you offload heavy, long

707
00:35:50.159 --> 00:35:53.400
<v Speaker 2>running tasks like processing and upload or sending emails to

708
00:35:53.440 --> 00:35:57.039
<v Speaker 2>background workers, significantly improving your web apps responsiveness for the user.

709
00:35:57.679 --> 00:36:01.159
<v Speaker 1>So, with that robust foundation in place to flask sucul

710
00:36:01.199 --> 00:36:04.760
<v Speaker 1>Alchemy Accelery, how do you inject the Rust module directly

711
00:36:04.800 --> 00:36:08.079
<v Speaker 1>into that flask gap for the seamless, tangible performance boost

712
00:36:08.079 --> 00:36:08.800
<v Speaker 1>we've been aiming for.

713
00:36:08.920 --> 00:36:11.840
<v Speaker 2>The integration can be quite elegant. You typically create a

714
00:36:11.840 --> 00:36:16.000
<v Speaker 2>flexible interface within your Python code, say a function coucfibnum.

715
00:36:16.320 --> 00:36:18.719
<v Speaker 2>This function could internally decide whether to call the pure

716
00:36:18.760 --> 00:36:21.480
<v Speaker 2>Python implementation or your compiled Rust implementation.

717
00:36:21.679 --> 00:36:23.440
<v Speaker 1>How would it decide maybe based on a.

718
00:36:23.360 --> 00:36:27.239
<v Speaker 2>Configuration setting, an environment variable, or even dynamically based on

719
00:36:27.280 --> 00:36:30.519
<v Speaker 2>the input size. You could use a simple calculation method

720
00:36:30.639 --> 00:36:34.760
<v Speaker 2>enum fere g, Piython or rus to control the switch.

721
00:36:35.039 --> 00:36:37.159
<v Speaker 1>And how does this look in practice in the web?

722
00:36:37.159 --> 00:36:39.440
<v Speaker 2>Back when you demonstrate this in a live web context,

723
00:36:39.480 --> 00:36:42.239
<v Speaker 2>you might have two endpoints calling the Rust backed endpoints

724
00:36:42.239 --> 00:36:45.280
<v Speaker 2>say Rust calculate dot number, you'll likely see it can

725
00:36:45.320 --> 00:36:49.119
<v Speaker 2>be dramatically faster. We observed it being potentially four times

726
00:36:49.159 --> 00:36:53.039
<v Speaker 2>faster or more for higher Fibonacci numbers. Compared to a

727
00:36:53.079 --> 00:36:56.599
<v Speaker 2>Python calculate dot number endpoint, calling the pure Python logic

728
00:36:56.639 --> 00:36:59.440
<v Speaker 2>for the same calculation, the difference becomes very apparent.

729
00:36:59.519 --> 00:37:02.039
<v Speaker 1>And for deployment, how does the Rust code get built?

730
00:37:02.320 --> 00:37:04.679
<v Speaker 2>As we mentioned for the Docker file, you simply modify

731
00:37:04.719 --> 00:37:07.400
<v Speaker 2>it to first install the Rust tool chain. These caimply

732
00:37:07.480 --> 00:37:11.239
<v Speaker 2>using the official Rust upscript dot, curl, HTTPS, dot s dot,

733
00:37:11.320 --> 00:37:16.760
<v Speaker 2>rust up dot r s SSFA, sy profile minimal, modify path,

734
00:37:17.079 --> 00:37:20.199
<v Speaker 2>and then as part of your Python package installation staff

735
00:37:20.239 --> 00:37:23.119
<v Speaker 2>like pipinstall dot the build back end set up tools

736
00:37:23.199 --> 00:37:26.719
<v Speaker 2>Rust mature automatically compiles your Rust extension module within that

737
00:37:26.800 --> 00:37:29.000
<v Speaker 2>Docker image before your application.

738
00:37:28.639 --> 00:37:32.519
<v Speaker 1>Starts, so it's built into the container image. Can Rust

739
00:37:32.559 --> 00:37:35.599
<v Speaker 1>even interact directly with the database in the setup or

740
00:37:35.679 --> 00:37:38.840
<v Speaker 1>is that strictly Python's domain via squalkamy No.

741
00:37:39.119 --> 00:37:42.039
<v Speaker 2>Rust can absolutely interact directly with the database and often

742
00:37:42.119 --> 00:37:45.199
<v Speaker 2>very efficiently too. You can use crates like Diesel, which

743
00:37:45.239 --> 00:37:49.000
<v Speaker 2>is a popular RM and querybuilder for Rust supporting Postcress School,

744
00:37:49.039 --> 00:37:50.880
<v Speaker 2>my school, and squite.

745
00:37:50.800 --> 00:37:53.679
<v Speaker 1>So Rust could bypass Python for database work.

746
00:37:53.880 --> 00:37:56.800
<v Speaker 2>Yes, this involves configuring a Diesel dot com l file

747
00:37:56.960 --> 00:38:00.880
<v Speaker 2>using the Diesel Cli tool to manage migrations, migration run

748
00:38:01.159 --> 00:38:04.639
<v Speaker 2>and to introspect your database schema. Are generating RUSS code

749
00:38:04.639 --> 00:38:07.440
<v Speaker 2>definitions for your tables into a schema dot r s file.

750
00:38:07.920 --> 00:38:09.960
<v Speaker 2>There are even tools like Diesel x that can help

751
00:38:10.000 --> 00:38:14.000
<v Speaker 2>autogenerate your database model strucks models dot rs from the schema.

752
00:38:14.119 --> 00:38:16.480
<v Speaker 1>Then the rest code talks SQL exactly.

753
00:38:16.599 --> 00:38:19.719
<v Speaker 2>Your Rust extension module can then directly perform complex queries

754
00:38:19.800 --> 00:38:23.199
<v Speaker 2>or bulk operations against the database using Diesel, potentially much

755
00:38:23.239 --> 00:38:26.039
<v Speaker 2>faster than going through Python and skoalcomy for certain tasks,

756
00:38:26.239 --> 00:38:28.280
<v Speaker 2>and then expose just the final results back to your

757
00:38:28.320 --> 00:38:32.039
<v Speaker 2>Python Flask application. This opens up even more possibilities for

758
00:38:32.079 --> 00:38:34.760
<v Speaker 2>optimizing performance critical database interactions.

759
00:38:35.119 --> 00:38:38.800
<v Speaker 1>Okay, we've covered an incredible amount of ground here. Let's

760
00:38:38.800 --> 00:38:41.519
<v Speaker 1>try to boil this down to some best practices for

761
00:38:41.599 --> 00:38:46.079
<v Speaker 1>creating a truly harmonious blend between Python and Rust. What's

762
00:38:46.119 --> 00:38:48.760
<v Speaker 1>the main guiding principle for a successful integration.

763
00:38:49.800 --> 00:38:53.679
<v Speaker 2>I think the guiding principle is elegantly simple. Keep it simple,

764
00:38:54.400 --> 00:38:56.719
<v Speaker 2>or maybe use the right tool for the job.

765
00:38:57.000 --> 00:38:59.280
<v Speaker 1>Don't just rewrite everything in Rust for the sake of

766
00:38:59.320 --> 00:39:00.159
<v Speaker 1>it exactly.

767
00:39:00.519 --> 00:39:03.280
<v Speaker 2>Pythons should remain your go to for high level tasks

768
00:39:03.679 --> 00:39:08.519
<v Speaker 2>building the web application framework, general data manipulation, scripting, machine learning,

769
00:39:08.840 --> 00:39:12.519
<v Speaker 2>where rapid development and its rich library ecosystem key. You

770
00:39:12.559 --> 00:39:16.719
<v Speaker 2>should reserve rest for those specific identified critical performance bottlenecks,

771
00:39:16.880 --> 00:39:20.400
<v Speaker 2>low level system programming, or areas where memory safety and

772
00:39:20.519 --> 00:39:25.000
<v Speaker 2>explicit concurrency control are absolutely paramount Be strategic.

773
00:39:24.599 --> 00:39:27.480
<v Speaker 1>And when you're structuring the interfaces between the two languages,

774
00:39:27.519 --> 00:39:30.679
<v Speaker 1>how should you approach that? For optimal developer experience, especially

775
00:39:30.679 --> 00:39:33.519
<v Speaker 1>for the Python developers using the module, you should.

776
00:39:33.239 --> 00:39:37.079
<v Speaker 2>Aim to maintain Python centric interfaces. Do the heavy lifting,

777
00:39:37.320 --> 00:39:40.639
<v Speaker 2>the number crunching the low level operations in Rust for speed,

778
00:39:40.960 --> 00:39:44.719
<v Speaker 2>but keep the data formation, the API design, access patterns,

779
00:39:44.760 --> 00:39:47.840
<v Speaker 2>and maybe even visualization entirely in Python.

780
00:39:48.079 --> 00:39:49.480
<v Speaker 1>Can you give an example sure?

781
00:39:49.719 --> 00:39:52.719
<v Speaker 2>For instance, in a two D physics simulation, Rust could

782
00:39:52.719 --> 00:39:56.119
<v Speaker 2>efficiently calculate all the coordinates of a trajectory maybe millions

783
00:39:56.119 --> 00:39:59.320
<v Speaker 2>of points, and return that raw data, perhaps as a

784
00:39:59.360 --> 00:40:03.280
<v Speaker 2>simple list dictionary, back to Python. Python would then consume

785
00:40:03.320 --> 00:40:07.000
<v Speaker 2>those coordinates via an intuitive Python class or object, making

786
00:40:07.039 --> 00:40:09.920
<v Speaker 2>it seamless and familiar for the Python user. They don't

787
00:40:09.960 --> 00:40:12.079
<v Speaker 2>need to know the gritty details of the Rust.

788
00:40:11.880 --> 00:40:15.719
<v Speaker 1>Calculation, hide the Rust implementation details behind a nice Python

789
00:40:15.760 --> 00:40:16.960
<v Speaker 1>facade precisely.

790
00:40:17.320 --> 00:40:20.320
<v Speaker 2>This leverages each language's strengths. You can even use Python

791
00:40:20.360 --> 00:40:23.039
<v Speaker 2>meta classes, as we mentioned with singletons, to further enhance

792
00:40:23.079 --> 00:40:26.320
<v Speaker 2>this by creating sophisticated wrappers around your Rust functionalities that

793
00:40:26.360 --> 00:40:28.920
<v Speaker 2>feel completely native to Python developers.

794
00:40:29.440 --> 00:40:33.280
<v Speaker 1>Rust's trait system seems unique and powerful compared to Python's classes.

795
00:40:33.360 --> 00:40:36.199
<v Speaker 1>How can you leverage that for defining behavior? Maybe even

796
00:40:36.239 --> 00:40:37.679
<v Speaker 1>across the language boundary?

797
00:40:37.920 --> 00:40:42.119
<v Speaker 2>Traits and Rust are super powerful. They define shared behavior

798
00:40:42.199 --> 00:40:45.719
<v Speaker 2>for structs somewhat similar to interfaces in languages like Java

799
00:40:45.920 --> 00:40:50.719
<v Speaker 2>or Go, or maybe conceptually like Python's abstract based classes ABC's.

800
00:40:51.079 --> 00:40:54.119
<v Speaker 2>Combined with duct typing, they let you define capabilities like

801
00:40:54.159 --> 00:40:57.159
<v Speaker 2>what For example, you could create speak, clinical skills, and

802
00:40:57.199 --> 00:41:00.760
<v Speaker 2>advance medical traits in Rust. Then you implement these specific

803
00:41:00.840 --> 00:41:04.880
<v Speaker 2>traits for different people's structs like patient maybe only implements speak,

804
00:41:05.400 --> 00:41:10.480
<v Speaker 2>nurse implement speak and clinical skills or doctor implements.

805
00:41:09.960 --> 00:41:12.480
<v Speaker 1>All three And this allows for polymorphism.

806
00:41:12.679 --> 00:41:15.800
<v Speaker 2>Yes, it enables polymorphism functions can accept any struck that

807
00:41:15.840 --> 00:41:19.599
<v Speaker 2>implements a specific trait, often using syntax like impletratee or

808
00:41:19.599 --> 00:41:23.360
<v Speaker 2>and in trait. This ensures consistent behavior and contracts across

809
00:41:23.400 --> 00:41:24.440
<v Speaker 2>different concrete types.

810
00:41:24.639 --> 00:41:27.079
<v Speaker 1>How does that translate to Python when using pio three.

811
00:41:27.400 --> 00:41:31.480
<v Speaker 2>Interestingly, when exposing Rust strucks that implement traits to Python

812
00:41:31.840 --> 00:41:36.159
<v Speaker 2>using PIO three's hashtag et opi class, these trait implementations

813
00:41:36.360 --> 00:41:40.119
<v Speaker 2>often translate very naturally into direct attributes or methods on

814
00:41:40.159 --> 00:41:43.559
<v Speaker 2>the resulting Python class, making them immediately usable in a

815
00:41:43.559 --> 00:41:47.400
<v Speaker 2>Pythonic way. For instance, the Advanced Medical trait might expose diagnose,

816
00:41:47.480 --> 00:41:50.800
<v Speaker 2>and prescribe methods that become directly callable on the doctor

817
00:41:50.840 --> 00:41:51.559
<v Speaker 2>Python object.

818
00:41:51.760 --> 00:41:55.360
<v Speaker 1>And finally, for parallelization, what's the best practice for using

819
00:41:55.440 --> 00:41:58.880
<v Speaker 1>rayon effectively? We said it was easy, but are there caveats?

820
00:42:00.079 --> 00:42:02.960
<v Speaker 2>Rayon crate provides an incredibly simple way to achieve data

821
00:42:02.960 --> 00:42:08.119
<v Speaker 2>parallelism in Rust using parallel iterators comperiator. The crucial consideration, though,

822
00:42:08.159 --> 00:42:08.800
<v Speaker 2>is overhead.

823
00:42:08.960 --> 00:42:11.280
<v Speaker 1>There's a cost to setting up the parallysm.

824
00:42:11.039 --> 00:42:13.800
<v Speaker 2>Exactly, there's a non zero setup cost to coordinating the

825
00:42:13.800 --> 00:42:17.519
<v Speaker 2>threadpool and distributing the work for very small tasks like

826
00:42:17.639 --> 00:42:20.840
<v Speaker 2>processing a vector of just five numbers a normal sequential loop,

827
00:42:20.880 --> 00:42:24.039
<v Speaker 2>and Rust might actually be faster than using Rayon. The

828
00:42:24.079 --> 00:42:25.280
<v Speaker 2>overhead outweighs the benefit.

829
00:42:25.400 --> 00:42:27.000
<v Speaker 1>But for larger tasks.

830
00:42:26.800 --> 00:42:29.559
<v Speaker 2>But as the data size and the computational load per

831
00:42:29.599 --> 00:42:33.559
<v Speaker 2>item increase, for example calculating Fibonacci numbers up to thirty

832
00:42:33.559 --> 00:42:37.639
<v Speaker 2>three or higher for a list of inputs, Rayon's benefits

833
00:42:37.679 --> 00:42:41.599
<v Speaker 2>become immediately clear and substantial. It's about knowing when the

834
00:42:41.639 --> 00:42:44.639
<v Speaker 2>overhead is worth the game profile your code.

835
00:42:44.960 --> 00:42:48.239
<v Speaker 1>Wow, what an absolutely incredible journey. We've taken a really

836
00:42:48.280 --> 00:42:51.320
<v Speaker 1>deep dive through the intricacies of Rust, haven't we. From

837
00:42:51.360 --> 00:42:55.320
<v Speaker 1>its foundational principles of memory safety and strict typing.

838
00:42:55.159 --> 00:42:57.639
<v Speaker 2>All the way to its powerful tooling like cargo, and

839
00:42:57.719 --> 00:43:01.079
<v Speaker 2>it's surprisingly seamless integration with via pio three.

840
00:43:01.320 --> 00:43:05.519
<v Speaker 1>Yeah, we've seen how you can build truly robust performance systems.

841
00:43:05.719 --> 00:43:09.239
<v Speaker 1>Whether it's a small, lightning fast calculation module tucked inside

842
00:43:09.280 --> 00:43:12.960
<v Speaker 1>your Python code or a full fledged flask Web application

843
00:43:13.119 --> 00:43:14.719
<v Speaker 1>leveraging Rust for critical parts.

844
00:43:14.760 --> 00:43:17.000
<v Speaker 2>It's a powerful combination when used thoughtfully.

845
00:43:17.360 --> 00:43:20.159
<v Speaker 1>You listening now hopefully have a much deeper understanding of

846
00:43:20.199 --> 00:43:23.679
<v Speaker 1>how to leverage Python's rapid development cycle and rich ecosystem

847
00:43:23.719 --> 00:43:28.719
<v Speaker 1>for your complex application logic, while surgically infusing the raw speed, reliability,

848
00:43:28.760 --> 00:43:32.400
<v Speaker 1>and concurrency benefits of Rust into your most performance critical.

849
00:43:31.960 --> 00:43:33.480
<v Speaker 2>Components, getting the best of both.

850
00:43:33.599 --> 00:43:36.840
<v Speaker 1>So here's the final thought. In a world drowning in data,

851
00:43:36.920 --> 00:43:40.519
<v Speaker 1>where every millisecond can translate into real world impact or

852
00:43:40.639 --> 00:43:44.800
<v Speaker 1>user satisfaction, how will this deeper understanding of Python and

853
00:43:44.920 --> 00:43:49.559
<v Speaker 1>Rust influence your next architectural decision? How might you start

854
00:43:49.599 --> 00:43:52.960
<v Speaker 1>to identify the hitting performance bottlenecks lurking in your current

855
00:43:53.000 --> 00:43:58.000
<v Speaker 1>projects and strategically introduce Rust to truly turbocharge them, all

856
00:43:58.039 --> 00:44:00.840
<v Speaker 1>while maintaining that developer friendly expert orian's Python is so

857
00:44:00.880 --> 00:44:01.360
<v Speaker 1>famous for.

858
00:44:01.679 --> 00:44:03.239
<v Speaker 2>It really opens up possibilities.

859
00:44:03.280 --> 00:44:04.239
<v Speaker 1>Food for thought, isn't it
